Got fix working for issues editing

This commit is contained in:
Vincent Allen
2025-09-06 09:33:41 -04:00
parent 77318e87d1
commit 781793a27f
561 changed files with 15420 additions and 1067 deletions

View File

@@ -103,8 +103,7 @@
<MudGrid Justify="Justify.FlexEnd" Class="pt-6"> <MudGrid Justify="Justify.FlexEnd" Class="pt-6">
<MudItem> <MudItem>
<MudCheckBox Label="Publicly Visible" T="bool"></MudCheckBox> <MudCheckBox Label="Publicly Visible" T="bool" @bind-Value=Model.IsPublicallyVisible></MudCheckBox>
@*<MudCheckBox Label="Publicly Visible" T="bool" @bind-Value=Model.IsPublic></MudCheckBox>*@
</MudItem> </MudItem>
@if (FormButtonsEnabled) @if (FormButtonsEnabled)

View File

@@ -143,7 +143,7 @@
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="pb-2">Input the text transcription of the words on the artifact if applicable to aid the search engine.</MudText> <MudText Typo="Typo.caption" Color="Color.Secondary" Class="pb-2">Input the text transcription of the words on the artifact if applicable to aid the search engine.</MudText>
<MudDivider DividerType="DividerType.Middle"></MudDivider> <MudDivider DividerType="DividerType.Middle"></MudDivider>
<MudTextField T="string" <MudTextField T="string"
Value=_artifactTextContent Value=Model.FileTextContent
ValueChanged="OnArtifactTextContentChanged" ValueChanged="OnArtifactTextContentChanged"
Lines="5" Lines="5"
For="@(() => Model.FileTextContent)"></MudTextField> For="@(() => Model.FileTextContent)"></MudTextField>

View File

@@ -4,6 +4,7 @@
Filterable=false Filterable=false
SelectOnRowClick=true SelectOnRowClick=true
ServerData="new Func<GridState<ArtifactGroupingRowElement>, Task<GridData<ArtifactGroupingRowElement>>>(ServerReload)" ServerData="new Func<GridState<ArtifactGroupingRowElement>, Task<GridData<ArtifactGroupingRowElement>>>(ServerReload)"
@key=@ArtifactGroupingRows
@ref=@DataGrid> @ref=@DataGrid>
<ToolBarContent> <ToolBarContent>
@@ -58,6 +59,7 @@
@inject IArtifactGroupingProvider GroupingProvider; @inject IArtifactGroupingProvider GroupingProvider;
@inject IDialogService DialogService; @inject IDialogService DialogService;
@inject IArtifactGroupingProvider GroupingProvider; @inject IArtifactGroupingProvider GroupingProvider;
@inject ArtifactEntrySharedHelpers Helpers;
@code @code
{ {
@@ -114,7 +116,8 @@
if (result is not null && !result.Canceled) if (result is not null && !result.Canceled)
{ {
var validationModel = (ArtifactGroupingValidationModel)result.Data!; var validationModel = (ArtifactGroupingValidationModel)result.Data!;
await GroupingProvider.UpdateGroupingAsync(validationModel.ToArtifactGrouping());
await Helpers.OnGroupingPublished(validationModel);
await DataGrid.ReloadServerData(); await DataGrid.ReloadServerData();
} }

View File

@@ -17,8 +17,10 @@ public class ArtifactEntrySharedHelpers
IDbContextFactory<ApplicationDbContext> DbContextFactory { get; set; } IDbContextFactory<ApplicationDbContext> DbContextFactory { get; set; }
IArtifactGroupingProvider GroupingProvider { get; set; }
public ArtifactEntrySharedHelpers(IArtifactDefectProvider defectsProvider, IArtifactStorageLocationProvider storageLocationProvider, IArchiveEntryTagProvider tagsProvider, IArtifactTypeProvider typesProvider, IListedNameProvider listedNamesProvider, IDbContextFactory<ApplicationDbContext> contextFactory)
public ArtifactEntrySharedHelpers(IArtifactDefectProvider defectsProvider, IArtifactStorageLocationProvider storageLocationProvider, IArchiveEntryTagProvider tagsProvider, IArtifactTypeProvider typesProvider, IListedNameProvider listedNamesProvider, IDbContextFactory<ApplicationDbContext> contextFactory, IArtifactGroupingProvider groupingProvider)
{ {
DefectsProvider = defectsProvider; DefectsProvider = defectsProvider;
StorageLocationProvider = storageLocationProvider; StorageLocationProvider = storageLocationProvider;
@@ -26,6 +28,7 @@ public class ArtifactEntrySharedHelpers
TypesProvider = typesProvider; TypesProvider = typesProvider;
ListedNameProvider = listedNamesProvider; ListedNameProvider = listedNamesProvider;
DbContextFactory = contextFactory; DbContextFactory = contextFactory;
GroupingProvider = groupingProvider;
} }
public async Task<IEnumerable<string>> SearchDefects(string value, CancellationToken cancellationToken) public async Task<IEnumerable<string>> SearchDefects(string value, CancellationToken cancellationToken)
@@ -103,214 +106,155 @@ public class ArtifactEntrySharedHelpers
return names.Select(p => p.Value); return names.Select(p => p.Value);
} }
/*
public async Task OnGroupingPublished(ArtifactGroupingValidationModel model) public async Task OnGroupingPublished(ArtifactGroupingValidationModel model)
{ {
await using var context = await DbContextFactory.CreateDbContextAsync(); await using var context = await DbContextFactory.CreateDbContextAsync();
var grouping = model.ToArtifactGrouping(); var grouping = model.ToArtifactGrouping();
// Caches to track entities processed within this transaction // The old logic for attaching the category is still good.
var processedFilePaths = new Dictionary<string, FilePathListing>(); context.Attach(grouping.Category);
var processedLocations = new Dictionary<string, ArtifactStorageLocation>();
var processedTags = new Dictionary<string, ArtifactEntryTag>();
var processedDefects = new Dictionary<string, ArtifactDefect>();
var processedTypes = new Dictionary<string, ArtifactType>();
var processedNames = new Dictionary<string, ListedName>();
// Process File Paths for each entry first // 1. Handle ArtifactType (no change, this was fine)
foreach (var entry in grouping.ChildArtifactEntries) if (grouping.Type is not null)
{ {
if (entry.Files is { Count: > 0 }) var existingType = await context.ArtifactTypes
.FirstOrDefaultAsync(t => t.Name == grouping.Type.Name);
if (existingType is not null)
{ {
var correctedFileList = new List<FilePathListing>(); grouping.Type = existingType;
foreach (var fileListing in entry.Files)
{
var path = fileListing.Path;
if (string.IsNullOrWhiteSpace(path)) continue;
if (processedFilePaths.TryGetValue(path, out var trackedFile))
{
correctedFileList.Add(trackedFile);
}
else
{
var existingFile = await context.ArtifactFilePaths
.FirstOrDefaultAsync(f => f.Path == path);
if (existingFile != null)
{
correctedFileList.Add(existingFile);
processedFilePaths[path] = existingFile;
}
else
{
correctedFileList.Add(fileListing);
processedFilePaths[path] = fileListing;
}
}
}
entry.Files = correctedFileList;
} }
} }
// Process all other related entities for each entry // 2. Process ChildArtifactEntries
foreach (var entry in grouping.ChildArtifactEntries) foreach (var entry in grouping.ChildArtifactEntries)
{ {
// Attach entry to its parent grouping // Handle ArtifactStorageLocation (no change, this was fine)
var existingLocation = await context.ArtifactStorageLocations
.FirstOrDefaultAsync(l => l.Location == entry.StorageLocation.Location);
if (existingLocation is not null)
{
entry.StorageLocation = existingLocation;
}
// Handle Defects
if (entry.Defects is not null && entry.Defects.Any())
{
var defectDescriptions = entry.Defects.Select(d => d.Description).ToList();
var existingDefects = await context.ArtifactDefects
.Where(d => defectDescriptions.Contains(d.Description))
.ToListAsync();
// Replace in-memory defects with existing ones
for (int i = 0; i < entry.Defects.Count; i++)
{
var existingDefect = existingDefects
.FirstOrDefault(ed => ed.Description == entry.Defects[i].Description);
if (existingDefect is not null)
{
entry.Defects[i] = existingDefect;
}
}
}
// Handle ListedNames
if (entry.ListedNames is not null && entry.ListedNames.Any())
{
var listedNamesValues = entry.ListedNames.Select(n => n.Value).ToList();
var existingNames = await context.ArtifactAssociatedNames
.Where(n => listedNamesValues.Contains(n.Value))
.ToListAsync();
for (int i = 0; i < entry.ListedNames.Count; i++)
{
var existingName = existingNames
.FirstOrDefault(en => en.Value == entry.ListedNames[i].Value);
if (existingName is not null)
{
entry.ListedNames[i] = existingName;
}
}
}
// Handle Tags
if (entry.Tags is not null && entry.Tags.Any())
{
var tagNames = entry.Tags.Select(t => t.Name).ToList();
var existingTags = await context.ArtifactEntryTags
.Where(t => tagNames.Contains(t.Name))
.ToListAsync();
for (int i = 0; i < entry.Tags.Count; i++)
{
var existingTag = existingTags
.FirstOrDefault(et => et.Name == entry.Tags[i].Name);
if (existingTag is not null)
{
entry.Tags[i] = existingTag;
}
}
}
// 💡 NEW: Handle pre-existing FilePathListings
// This is the key change to resolve the exception
if (entry.Files is not null)
{
foreach (var filepath in entry.Files)
{
// The issue is trying to add a new entity that has an existing primary key.
// Since you stated that all files are pre-added, you must attach them.
// Attach() tells EF Core to track the entity, assuming it already exists.
context.Attach(filepath);
// Also ensure the parent-child relationship is set correctly, though it's likely set by ToArtifactGrouping
filepath.ParentArtifactEntry = entry;
}
}
// Tag each entry with the parent grouping so it is linked correctly in the database
entry.ArtifactGrouping = grouping; entry.ArtifactGrouping = grouping;
// --- Process Storage Location ---
var locationName = entry.StorageLocation?.Location;
if (!string.IsNullOrWhiteSpace(locationName))
{
if (processedLocations.TryGetValue(locationName, out var trackedLocation))
{
entry.StorageLocation = trackedLocation;
}
else
{
var existingLocation = await context.ArtifactStorageLocations
.FirstOrDefaultAsync(l => l.Location == locationName);
if (existingLocation != null)
{
entry.StorageLocation = existingLocation;
processedLocations[locationName] = existingLocation;
}
else
{
processedLocations[locationName] = entry.StorageLocation;
}
}
}
// --- Process Tags ---
if (entry.Tags is { Count: > 0 })
{
var correctedTagList = new List<ArtifactEntryTag>();
foreach (var tag in entry.Tags)
{
var tagName = tag.Name;
if (string.IsNullOrWhiteSpace(tagName)) continue;
if (processedTags.TryGetValue(tagName, out var trackedTag))
{
correctedTagList.Add(trackedTag);
}
else
{
var existingTag = await context.ArtifactEntryTags.FirstOrDefaultAsync(t => t.Name == tagName);
if (existingTag != null)
{
correctedTagList.Add(existingTag);
processedTags[tagName] = existingTag;
}
else
{
correctedTagList.Add(tag);
processedTags[tagName] = tag;
}
}
}
entry.Tags = correctedTagList;
}
// --- Process Defects ---
if (entry.Defects is { Count: > 0 })
{
var correctedDefectList = new List<ArtifactDefect>();
foreach (var defect in entry.Defects)
{
var defectDesc = defect.Description;
if (string.IsNullOrWhiteSpace(defectDesc)) continue;
if (processedDefects.TryGetValue(defectDesc, out var trackedDefect))
{
correctedDefectList.Add(trackedDefect);
}
else
{
var existingDefect = await context.ArtifactDefects.FirstOrDefaultAsync(d => d.Description == defectDesc);
if (existingDefect != null)
{
correctedDefectList.Add(existingDefect);
processedDefects[defectDesc] = existingDefect;
}
else
{
correctedDefectList.Add(defect);
processedDefects[defectDesc] = defect;
}
}
}
entry.Defects = correctedDefectList;
}
// --- Process Types ---
if (entry.Type is not null)
{
var typeName = entry.Type.Name;
if (!string.IsNullOrWhiteSpace(typeName))
{
if (processedTypes.TryGetValue(typeName, out var trackedType))
{
entry.Type = trackedType;
}
else
{
var existingType = await context.ArtifactTypes.FirstOrDefaultAsync(t => t.Name == typeName);
if (existingType != null)
{
entry.Type = existingType;
processedTypes[typeName] = existingType;
}
else
{
processedTypes[typeName] = entry.Type;
}
}
}
}
// --- Process Listed Names ---
if (entry.ListedNames is { Count: > 0 })
{
var correctedNameList = new List<ListedName>();
foreach (var name in entry.ListedNames)
{
var nameValue = name.Value;
if (string.IsNullOrWhiteSpace(nameValue)) continue;
if (processedNames.TryGetValue(nameValue, out var trackedName))
{
correctedNameList.Add(trackedName);
}
else
{
var existingName = await context.ArtifactAssociatedNames
.FirstOrDefaultAsync(n => n.Value == nameValue);
if (existingName != null)
{
correctedNameList.Add(existingName);
processedNames[nameValue] = existingName;
}
else
{
correctedNameList.Add(name);
processedNames[nameValue] = name;
}
}
}
entry.ListedNames = correctedNameList;
}
} }
if (grouping.Category != null && grouping.Category.Id > 0) // 3. Add the main grouping object and let EF Core handle the graph
{ // The previous issues with the graph are resolved, so this line should now work.
context.Attach(grouping.Category);
}
// Add the entire graph. EF Core will correctly handle new vs. existing entities.
context.ArtifactGroupings.Add(grouping); context.ArtifactGroupings.Add(grouping);
// 4. Save all changes in a single transaction
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
*/
public async Task OnGroupingPublished(ArtifactGroupingValidationModel model)
{
// The OnGroupingPublished method in this class should not contain DbContext logic.
// It should orchestrate the data flow by calling the appropriate provider methods.
var isNew = model.Id == 0 || model.Id is null;
// Convert the validation model to an entity
var grouping = model.ToArtifactGrouping();
if (isNew)
{
// For a new grouping, use the CreateGroupingAsync method.
// The provider method will handle the file path logic.
await GroupingProvider.CreateGroupingAsync(grouping);
}
else
{
// For an existing grouping, use the UpdateGroupingAsync method.
// The provider method will handle the change tracking.
await GroupingProvider.UpdateGroupingAsync(grouping);
}
}
} }

View File

@@ -82,7 +82,7 @@ public class ArtifactEntryValidationModel
List<ListedName> listedNames = new(); List<ListedName> listedNames = new();
foreach (var name in ListedNames) foreach (var name in ListedNames)
{ {
listedNames.Add(new ListedName() { ParentArtifactEntry=entry, Value=name}); listedNames.Add(new ListedName() { Value=name });
} }
entry.ListedNames = listedNames; entry.ListedNames = listedNames;

View File

@@ -48,7 +48,7 @@ public class ArtifactGroupingValidationModel : IValidatableObject
IdentifierFields = identifierFields, IdentifierFields = identifierFields,
IsPublicallyVisible = true, IsPublicallyVisible = true,
ChildArtifactEntries = entries, ChildArtifactEntries = entries,
Type = Type Type = new ArtifactType() { Name = Type }
}; };
// Create the parent link // Create the parent link
@@ -113,7 +113,7 @@ public class ArtifactGroupingValidationModel : IValidatableObject
Description = grouping.Description, Description = grouping.Description,
IdentifierFieldValues = identifierFields, IdentifierFieldValues = identifierFields,
IsPublicallyVisible = grouping.IsPublicallyVisible, IsPublicallyVisible = grouping.IsPublicallyVisible,
Type = grouping.Type, Type = grouping.Type.Name
}; };
} }

View File

@@ -12,11 +12,14 @@ public class ArtifactGroupingRowElement
public bool IsPublicallyVisible { get; set; } public bool IsPublicallyVisible { get; set; }
public override bool Equals(object? o) public bool Equals(ArtifactGroupingRowElement? other)
{ {
var other = o as ArtifactGroupingRowElement; if (other is null) return false;
return other?.Id == Id; if (ReferenceEquals(this, other)) return true;
return Id == other.Id; // Compare based on the unique Id
} }
public override bool Equals(object? obj) => Equals(obj as ArtifactGroupingRowElement);
public override int GetHashCode() => Id.GetHashCode(); public override int GetHashCode() => Id.GetHashCode();
} }

View File

@@ -0,0 +1,5 @@
<h3>ArchiveEntryDisplay</h3>
@code {
}

View File

@@ -0,0 +1,40 @@
@page "/archive/{GroupingIdString}"
@using OpenArchival.DataAccess;
@inject IArtifactGroupingProvider GroupingProvider;
@inject NavigationManager NavigationManager;
@if (_artifactGrouping is not null)
{
<MudText Typo="Typo.h1">@_artifactGrouping.Title</MudText>
}
@code {
[Parameter]
public string GroupingIdString { get; set; }
/// <summary>
/// The converted grouping id from the URL
/// </summary>
private int _groupingId { get; set; }
private ArtifactGrouping _artifactGrouping { get; set; } = default!;
protected override async Task OnParametersSetAsync()
{
_groupingId = int.Parse(GroupingIdString);
var grouping = await GroupingProvider.GetGroupingAsync(_groupingId);
if (grouping is null)
{
NavigationManager.NavigateTo("/grouping-not-found");
}
_artifactGrouping = grouping!;
StateHasChanged();
await base.OnParametersSetAsync();
}
}

View File

@@ -0,0 +1,10 @@
@page "/grouping-not-found"
<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
<MudText Typo="Typo.h1" Align="Align.Center">Artifact Not Found!</MudText>
</MudPaper>
@code {
}

View File

@@ -0,0 +1,5 @@
<h3>FileDisplayBase</h3>
@code {
}

View File

@@ -0,0 +1,5 @@
<h3>FileDisplayImage</h3>
@code {
}

View File

@@ -0,0 +1,5 @@
namespace OpenArchival.Blazor;
public interface IFileDisplayComponent
{
}

View File

@@ -20,6 +20,7 @@ COPY . .
WORKDIR "/src/OpenArchival.Blazor" WORKDIR "/src/OpenArchival.Blazor"
RUN dotnet build "./OpenArchival.Blazor.csproj" -c $BUILD_CONFIGURATION -o /app/build RUN dotnet build "./OpenArchival.Blazor.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage # This stage is used to publish the service project to be copied to the final stage
FROM build AS publish FROM build AS publish
ARG BUILD_CONFIGURATION=Release ARG BUILD_CONFIGURATION=Release
@@ -29,4 +30,8 @@ RUN dotnet publish "./OpenArchival.Blazor.csproj" -c $BUILD_CONFIGURATION -o /ap
FROM base AS final FROM base AS final
WORKDIR /app WORKDIR /app
COPY --from=publish /app/publish . COPY --from=publish /app/publish .
# Create the folder that will be used to store uploads
RUN mkdir /app/OpenArchivalUploads
ENTRYPOINT ["dotnet", "OpenArchival.Blazor.dll"] ENTRYPOINT ["dotnet", "OpenArchival.Blazor.dll"]

View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile> <ActiveDebugProfile>https</ActiveDebugProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -18,7 +18,7 @@
}, },
"FileUploadOptions": { "FileUploadOptions": {
"MaxUploadSizeBytes": 2147483648, "MaxUploadSizeBytes": 2147483648,
"UploadFolderPath": "/OpenArchivalUploads", "UploadFolderPath": "/app/OpenArchivalUploads",
"MaxFileCount": 100 "MaxFileCount": 100
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -18,7 +18,7 @@
}, },
"FileUploadOptions": { "FileUploadOptions": {
"MaxUploadSizeBytes": 2147483648, "MaxUploadSizeBytes": 2147483648,
"UploadFolderPath": "/OpenArchivalUploads", "UploadFolderPath": "/app/OpenArchivalUploads",
"MaxFileCount": 100 "MaxFileCount": 100
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More