diff --git a/AStar.Dev.slnx b/AStar.Dev.slnx
index 04575dd..00f52f4 100644
--- a/AStar.Dev.slnx
+++ b/AStar.Dev.slnx
@@ -18,32 +18,29 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -65,6 +62,10 @@
+
+
+
+
@@ -93,9 +94,8 @@
-
-
+
+
diff --git a/src/_aspire/AStar.Dev.AppHost/Configurations/DatabaseUpdaterApiProjectConfigurator.cs b/src/_aspire/AStar.Dev.AppHost/Configurations/DatabaseUpdaterApiProjectConfigurator.cs
index 6eb1de1..ef64a64 100644
--- a/src/_aspire/AStar.Dev.AppHost/Configurations/DatabaseUpdaterApiProjectConfigurator.cs
+++ b/src/_aspire/AStar.Dev.AppHost/Configurations/DatabaseUpdaterApiProjectConfigurator.cs
@@ -5,10 +5,6 @@ namespace AStar.Dev.AppHost.Configurations;
public static class DatabaseUpdaterApiProjectConfigurator
{
- public record DatabaseUpdaterApiProjectConfig(string ProjectName);
-
- public static DatabaseUpdaterApiProjectConfig GetConfig() => new(AspireConstants.Services.DatabaseUpdater);
-
public static void Configure(
IDistributedApplicationBuilder builder,
IResourceBuilder filesDb,
@@ -16,8 +12,7 @@ public static void Configure(
IResourceBuilder sqlServer,
IResourceBuilder rabbitMq)
{
- DatabaseUpdaterApiProjectConfig config = GetConfig();
- _ = builder.AddProject(config.ProjectName)
+ _ = builder.AddProject(AspireConstants.Services.DatabaseUpdater)
.WithReference(filesDb)
.WaitFor(filesDb)
.WithReference(migrations)
diff --git a/src/_aspire/AStar.Dev.AppHost/Configurations/DistributedApplicationBuilderExtensions.cs b/src/_aspire/AStar.Dev.AppHost/Configurations/DistributedApplicationBuilderExtensions.cs
index 8360764..693ac65 100644
--- a/src/_aspire/AStar.Dev.AppHost/Configurations/DistributedApplicationBuilderExtensions.cs
+++ b/src/_aspire/AStar.Dev.AppHost/Configurations/DistributedApplicationBuilderExtensions.cs
@@ -14,7 +14,7 @@ public static void AddApplicationProjects(this IDistributedApplicationBuilder bu
IResourceBuilder astarDb = sqlServer.AddDatabase(AspireConstants.Sql.AStarDb);
IResourceBuilder migrations = MigrationsConfigurator.Configure(builder, astarDb, sqlServer, sqlSaUserPassword, sqlAdminUserPassword, sqlFilesUserPassword, sqlUsageUserPassword);
IResourceBuilder rabbitMq = RabbitMqConfigurator.Configure(builder);
- DatabaseUpdaterApiProjectConfigurator.Configure(builder, astarDb, migrations, sqlServer, rabbitMq);
+ //DatabaseUpdaterApiProjectConfigurator.Configure(builder, astarDb, migrations, sqlServer, rabbitMq);
UiProjectConfigurator.Configure(builder, astarDb, rabbitMq);
}
diff --git a/src/_aspire/AStar.Dev.AppHost/Configurations/SqlServerConfigurator.cs b/src/_aspire/AStar.Dev.AppHost/Configurations/SqlServerConfigurator.cs
index 47616f1..2141747 100644
--- a/src/_aspire/AStar.Dev.AppHost/Configurations/SqlServerConfigurator.cs
+++ b/src/_aspire/AStar.Dev.AppHost/Configurations/SqlServerConfigurator.cs
@@ -13,7 +13,7 @@ public static IResourceBuilder Configure(IDistributedAp
SqlServerConfig config = GetConfig();
return builder.AddSqlServer(config.ServerName, sqlPassword, config.Port)
.WithLifetime(ContainerLifetime.Persistent)
- //.WithDataBindMount(sqlMountDirectory)
+ .WithDataBindMount("/home/jason/databases")
.WithExternalHttpEndpoints();
}
}
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/FileClassificationJoined.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/FileClassificationJoined.cs
new file mode 100644
index 0000000..7561f57
--- /dev/null
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/FileClassificationJoined.cs
@@ -0,0 +1,25 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace AStar.Dev.Files.Classifications.Api.Endpoints.FileClassifications.V1;
+
+///
+/// Provides an extension method to join file classifications with their parent classifications.
+///
+public static class FileClassificationJoined
+{
+ ///
+ /// Joins file classifications with their associated parent classifications using a left join approach.
+ /// Each file classification is paired with its parent classification, if available.
+ ///
+ /// The database queryable set of file classifications.
+ /// An containing the joined file classification and parent classification data.
+ public static IQueryable JoinFileClassificationsToParents(this DbSet query)
+ => query.AsNoTracking()
+ .GroupJoin(
+ query,
+ fc => fc.ParentId,
+ p => p.Id,
+ (fc, parents) => new { fc, parents }
+ )
+ .SelectMany(x => x.parents.DefaultIfEmpty(), (x, p) => new FileClassifications( x.fc, p ));
+}
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/FileClassifications.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/FileClassifications.cs
new file mode 100644
index 0000000..4ddad7a
--- /dev/null
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/FileClassifications.cs
@@ -0,0 +1,15 @@
+namespace AStar.Dev.Files.Classifications.Api.Endpoints.FileClassifications.V1;
+
+///
+/// Represents the aggregation of a file classification and its optional parent file classification.
+/// This record is utilized to handle hierarchical relationships between file classifications,
+/// where a file classification can have a parent classification for organizational purposes.
+///
+///
+/// The primary file classification data, representing a specific classification instance.
+///
+///
+/// The optional parent classification, indicating its hierarchical relationship to the primary
+/// file classification. A null value signifies no parent classification.
+///
+public record FileClassifications(Infrastructure.FilesDb.Models.FileClassification FileClassification, Infrastructure.FilesDb.Models.FileClassification? Parent);
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationRequest.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationRequest.cs
index 8d90f98..349e030 100644
--- a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationRequest.cs
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationRequest.cs
@@ -9,7 +9,7 @@ namespace AStar.Dev.Files.Classifications.Api.Endpoints.FileClassifications.V1;
/// The object supports HTTP GET operation to access the required endpoint.
///
[UsedImplicitly]
-public record GetFileClassificationRequest(int CurrentPage = 1, int ItemsPerPage = 10) : IEndpointName
+public record GetFileClassificationRequest(int CurrentPage = 1, int ItemsPerPage = 10) : IEndpointName,IPagingParameters
{
///
public string Name => EndpointConstants.GetFileClassificationsGroupName;
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsHandler.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsHandler.cs
index 682457e..848e2e9 100644
--- a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsHandler.cs
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsHandler.cs
@@ -19,14 +19,29 @@ public async Task> HandleAsy
GetFileClassificationRequest fileClassifications, FilesContext filesContext,
CancellationToken cancellationToken)
{
- if(fileClassifications.ItemsPerPage > 50) fileClassifications = fileClassifications with { ItemsPerPage = 50 };
+ var pagingParams = PagingParams.CreateValid(fileClassifications);
- List classifications = await filesContext.FileClassifications
- .OrderBy(fc => fc.Name)
- .Skip(fileClassifications.CurrentPage - 1).Take(fileClassifications.ItemsPerPage)
- .Select(fc => new GetFileClassificationsResponse(fc.Id, fc.Name, fc.IncludeInSearch, fc.Celebrity))
+ return await filesContext.FileClassifications
+ .AsNoTracking()
+ .GroupJoin(
+ filesContext.FileClassifications.AsNoTracking(),
+ fc => fc.ParentId,
+ p => p.Id,
+ (fc, parents) => new { fc, parents }
+ )
+ .SelectMany(x => x.parents.DefaultIfEmpty(), (x, p) => new { x.fc, p })
+ .OrderBy(x => x.fc.Name)
+ .Skip(pagingParams.SkipValue)
+ .Take(pagingParams.PageSize)
+ .Select(x => new GetFileClassificationsResponse(
+ x.fc.Id,
+ x.fc.Name,
+ x.fc.IncludeInSearch,
+ x.fc.Celebrity,
+ x.fc.ParentId,
+ x.p != null ? x.p.Name : null,
+ x.fc.SearchLevel
+ ))
.ToListAsync(cancellationToken);
-
- return classifications;
}
}
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsResponse.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsResponse.cs
index fb24531..d6a0d50 100644
--- a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsResponse.cs
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/GetFileClassificationsResponse.cs
@@ -3,4 +3,4 @@ namespace AStar.Dev.Files.Classifications.Api.Endpoints.FileClassifications.V1;
///
/// Response model for file classification data
///
-public record GetFileClassificationsResponse(Guid Id, string Name, bool IncludeInSearch, bool Celebrity);
+public record GetFileClassificationsResponse(Guid Id, string Name, bool IncludeInSearch, bool Celebrity, Guid? ParentId, string? Parent = null, int SearchLevel = 2);
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/IPagingParameters.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/IPagingParameters.cs
new file mode 100644
index 0000000..e8c4b3f
--- /dev/null
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/IPagingParameters.cs
@@ -0,0 +1,20 @@
+namespace AStar.Dev.Files.Classifications.Api.Endpoints.FileClassifications.V1;
+
+///
+/// Defines the parameters necessary to support pagination functionality, including the current page
+/// and the number of items per page.
+/// Objects implementing this interface provide a standardized way to manage paginated data retrieval.
+///
+public interface IPagingParameters
+{
+ ///
+ /// Gets the current page number for pagination purposes.
+ /// This property specifies which page of results should be retrieved.
+ ///
+ int CurrentPage { get; }
+
+ ///
+ /// Gets the number of items to be displayed per page in a paginated request.
+ ///
+ int ItemsPerPage { get; }
+}
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/PagingParams.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/PagingParams.cs
new file mode 100644
index 0000000..937df0c
--- /dev/null
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Endpoints/FileClassifications/V1/PagingParams.cs
@@ -0,0 +1,25 @@
+namespace AStar.Dev.Files.Classifications.Api.Endpoints.FileClassifications.V1;
+
+internal sealed class PagingParams
+{
+ private PagingParams(int pageSize, int skipValue)
+ {
+ PageSize = pageSize;
+ SkipValue = skipValue;
+ }
+
+ public int PageSize { get; }
+
+ public int SkipValue { get; }
+
+ public static PagingParams CreateValid(IPagingParameters pagingParams)
+ {
+ var pageSize = pagingParams.ItemsPerPage <= 0
+ ? 10
+ : (pagingParams.ItemsPerPage > 50 ? 50 : pagingParams.ItemsPerPage);
+ var pageIndex = pagingParams.CurrentPage <= 0 ? 1 : pagingParams.CurrentPage;
+ var skip = (pageIndex - 1) * pageSize;
+
+ return new PagingParams(pageSize, skip);
+ }
+}
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/FileClassification.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/FileClassification.cs
index ff735a8..419971c 100644
--- a/src/modules/apis/AStar.Dev.Files.Classifications.Api/FileClassification.cs
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/FileClassification.cs
@@ -3,4 +3,4 @@
///
/// Represents a classification of files, containing details about name, search level, parent classification, and flags for celebrity status or search inclusion.
///
-public record FileClassification(Guid Id, int SearchLevel, Guid? ParentId, string Name, bool Celebrity, bool IncludeInSearch);
+public record FileClassification(Guid Id, int SearchLevel, string Name, Guid? ParentId, string? ParentName, bool Celebrity, bool IncludeInSearch);
diff --git a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Services/FileClassificationsService.cs b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Services/FileClassificationsService.cs
index 500f69d..64cc5be 100644
--- a/src/modules/apis/AStar.Dev.Files.Classifications.Api/Services/FileClassificationsService.cs
+++ b/src/modules/apis/AStar.Dev.Files.Classifications.Api/Services/FileClassificationsService.cs
@@ -21,5 +21,7 @@ public class FileClassificationsService2 : IFileClassificationsService2
///
/// A collection of file classification names.
public async Task> GetFileClassificationsAsync()
- => await _context.FileClassifications.Select(fc => new FileClassification(fc.Id, fc.SearchLevel, fc.ParentId, fc.Name, fc.Celebrity, fc.IncludeInSearch)).ToListAsync();
+ => await _context.FileClassifications
+ .Select(fc => new FileClassification(fc.Id, fc.SearchLevel, fc.Name, fc.ParentId, fc.ParentId.ToString(), fc.Celebrity, fc.IncludeInSearch))
+ .ToListAsync();
}
diff --git a/src/modules/astar-dev-database-updater/AStar.Dev.Database.Updater/appsettings.json b/src/modules/astar-dev-database-updater/AStar.Dev.Database.Updater/appsettings.json
index 113939e..0498a8a 100644
--- a/src/modules/astar-dev-database-updater/AStar.Dev.Database.Updater/appsettings.json
+++ b/src/modules/astar-dev-database-updater/AStar.Dev.Database.Updater/appsettings.json
@@ -23,7 +23,7 @@
},
"databaseUpdaterConfiguration": {
"rootDirectory": "/home/jason/Documents/Pictures/_lookat/",
- "mappingsFilePath": "/home/jason/Documents/Mappings.csv",
+ "mappingsFilePath": "/home/jason/Documents/File-Mappings.csv",
"softDeleteScheduledTime": "02:00",
"hardDeleteScheduledTime": "03:00",
"newFilesScheduledTime": "04:00",
diff --git a/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/AStar.Dev.Infrastructure.FilesDb.csproj b/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/AStar.Dev.Infrastructure.FilesDb.csproj
index 632faac..2d4191a 100644
--- a/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/AStar.Dev.Infrastructure.FilesDb.csproj
+++ b/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/AStar.Dev.Infrastructure.FilesDb.csproj
@@ -3,6 +3,7 @@
latest-recommended
True
+ true
@@ -54,6 +55,8 @@
+
+
diff --git a/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/Models/FileId.cs b/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/Models/FileId.cs
index b37bb08..47dea5c 100644
--- a/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/Models/FileId.cs
+++ b/src/nuget-packages/AStar.Dev.Infrastructure.FilesDb/Models/FileId.cs
@@ -1,9 +1,12 @@
+using AStar.Dev.Annotations;
+
namespace AStar.Dev.Infrastructure.FilesDb.Models;
///
/// Defines the FileId
///
-public record struct FileId()
+[StrongId]
+public partial record struct FileId()
{
/// The value of the File ID
public Guid Value { get; set; } = Guid.CreateVersion7();
diff --git a/src/source-generators/AStar.Dev.Annotations/AStar.Dev.Annotations.csproj b/src/source-generators/AStar.Dev.Annotations/AStar.Dev.Annotations.csproj
new file mode 100644
index 0000000..cfb3833
--- /dev/null
+++ b/src/source-generators/AStar.Dev.Annotations/AStar.Dev.Annotations.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netstandard2.0
+ preview
+ enable
+ enable
+
+
+
diff --git a/src/source-generators/AStar.Dev.Annotations/StrongIdAttribute.cs b/src/source-generators/AStar.Dev.Annotations/StrongIdAttribute.cs
new file mode 100644
index 0000000..3b62b8b
--- /dev/null
+++ b/src/source-generators/AStar.Dev.Annotations/StrongIdAttribute.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace AStar.Dev.Annotations;
+
+///
+/// Represents an attribute that can be used to mark a struct as a strong identifier.
+///
+///
+/// This attribute is used to annotate structures with a specific identifier type, typically
+/// to signify that the struct is a strongly typed id. The default identifier type is a GUID.
+///
+///
+/// The type of the identifier associated with the struct. By default, it is "System.Guid".
+///
+///
+/// Indicates whether the GUID used as the identifier for the struct conforms to the version 7 GUID format.
+///
+[AttributeUsage(AttributeTargets.Struct)]
+public sealed class StrongIdAttribute(string idType = "System.Guid", bool guidV7 = true) : Attribute
+{
+ ///
+ /// Gets the type of the identifier associated with a struct marked by the .
+ ///
+ ///
+ /// This property returns the string name of the identifier type. By default, it is "System.Guid".
+ /// The identifier type specifies the type used as an identifier for the struct.
+ ///
+ public string IdType { get; } = idType;
+
+ ///
+ /// Indicates whether the GUID used as the identifier for a struct marked by the
+ /// conforms to the version 7 GUID format.
+ ///
+ ///
+ /// Version 7 GUIDs are designed to facilitate time-based sorting and are a newer standard
+ /// compared to the traditional random GUIDs (version 4). When this property is set to true,
+ /// the identifier adopts the version 7 format.
+ ///
+ public bool GuidV7 { get; } = guidV7;
+}
diff --git a/src/source-generators/AStar.Dev.SourceGenerators/AStar.Dev.SourceGenerators.csproj b/src/source-generators/AStar.Dev.SourceGenerators/AStar.Dev.SourceGenerators.csproj
new file mode 100644
index 0000000..56651ec
--- /dev/null
+++ b/src/source-generators/AStar.Dev.SourceGenerators/AStar.Dev.SourceGenerators.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.0
+ preview
+ enable
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
diff --git a/src/source-generators/AStar.Dev.SourceGenerators/StrongIdGenerator.cs b/src/source-generators/AStar.Dev.SourceGenerators/StrongIdGenerator.cs
new file mode 100644
index 0000000..b78173a
--- /dev/null
+++ b/src/source-generators/AStar.Dev.SourceGenerators/StrongIdGenerator.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace AStar.Dev.SourceGenerators;
+
+[Generator]
+public sealed class StrongIdGenerator : IIncrementalGenerator
+{
+ private const string AttrFqn = "Annotations.StrongIdAttribute";
+
+ public void Initialize(IncrementalGeneratorInitializationContext ctx)
+ {
+ // Discover partial structs annotated with [StrongId]
+ IncrementalValuesProvider candidates = ctx.SyntaxProvider.ForAttributeWithMetadataName(
+ AttrFqn,
+ static (node, _) => node is StructDeclarationSyntax s && s.Modifiers.Any(m => m.Text == "partial"),
+ static (syntaxCtx, _) =>
+ {
+ var symbol = (INamedTypeSymbol)syntaxCtx.TargetSymbol;
+ AttributeData attr = syntaxCtx.Attributes[0];
+
+ var underlyingArg = attr.ConstructorArguments.Length == 1
+ ? attr.ConstructorArguments[0].Value?.ToString() ?? "System.Guid"
+ : "System.Guid";
+
+ return new StrongIdModel(
+ symbol.ContainingNamespace.IsGlobalNamespace ? null : symbol.ContainingNamespace.ToDisplayString(),
+ symbol.Name,
+ symbol.DeclaredAccessibility,
+ underlyingArg
+ );
+ });
+
+ // Use a custom comparer (no System.HashCode) so incremental semantics are stable
+ IncrementalValueProvider> models = candidates.WithComparer(StrongIdModelEqualityComparer.Instance).Collect();
+
+ ctx.RegisterSourceOutput(models, static (spc, batch) =>
+ {
+ foreach (StrongIdModel? model in batch) spc.AddSource($"{model.Name}.StrongId.g.cs", Emit(model));
+ });
+ }
+
+ private static string Emit(StrongIdModel m)
+ {
+ var ns = m.Namespace is null ? null : $"namespace {m.Namespace};";
+ var acc = m.Accessibility.ToString().ToLowerInvariant();
+ var t = m.UnderlyingTypeDisplay;
+
+ // detect special-cases
+ var isGuid = string.Equals(t, "System.Guid", StringComparison.Ordinal) ||
+ string.Equals(t, "Guid", StringComparison.Ordinal);
+ var isString = string.Equals(t, "System.String", StringComparison.Ordinal) ||
+ string.Equals(t, "string", StringComparison.Ordinal);
+
+ var toStringBody = isString ? "_value ?? string.Empty" : "_value.ToString()";
+ var getHashBody = isString
+ ? "(_value == null ? 0 : _value.GetHashCode())"
+ : $"System.Collections.Generic.EqualityComparer<{t}>.Default.GetHashCode(_value)";
+
+ var sb = new StringBuilder();
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ if (ns is not null) sb.AppendLine(ns).AppendLine();
+
+ sb.AppendLine(
+ $$"""
+ {{acc}} readonly partial struct {{m.Name}} : System.IEquatable<{{m.Name}}>
+ {
+ private readonly {{t}} _value;
+ public {{m.Name}}({{t}} value) => _value = value;
+
+ public static implicit operator {{t}}({{m.Name}} id) => id._value;
+ public static explicit operator {{m.Name}}({{t}} value) => new(value);
+
+ public bool Equals({{m.Name}} other) => System.Collections.Generic.EqualityComparer<{{t}}>.Default.Equals(_value, other._value);
+ public override bool Equals(object? obj) => obj is {{m.Name}} other && Equals(other);
+ public override int GetHashCode() => {{getHashBody}};
+ public override string ToString() => {{toStringBody}};
+ """);
+
+ if (isGuid)
+ {
+ sb.AppendLine(
+ $$"""
+
+ public static {{m.Name}} New() => new(System.Guid.NewGuid());
+
+ public static bool TryParse(string? s, out {{m.Name}} value)
+ {
+ var ok = System.Guid.TryParse(s, out var g);
+ value = ok ? new(g) : default;
+ return ok;
+ }
+ """);
+ }
+
+ sb.AppendLine("}");
+ return sb.ToString();
+ }
+
+ private sealed class StrongIdModel(
+ string? ns,
+ string name,
+ Accessibility accessibility,
+ string underlyingTypeDisplay)
+ {
+ public string? Namespace { get; } = ns;
+ public string Name { get; } = name;
+ public Accessibility Accessibility { get; } = accessibility;
+ public string UnderlyingTypeDisplay { get; } = underlyingTypeDisplay;
+ }
+
+ private sealed class StrongIdModelEqualityComparer : IEqualityComparer
+ {
+ public static readonly StrongIdModelEqualityComparer Instance = new();
+
+ public bool Equals(StrongIdModel? x, StrongIdModel? y)
+ {
+ if (ReferenceEquals(x, y)) return true;
+ if (x is null || y is null) return false;
+
+ return string.Equals(x.Namespace, y.Namespace, StringComparison.Ordinal)
+ && string.Equals(x.Name, y.Name, StringComparison.Ordinal)
+ && string.Equals(x.UnderlyingTypeDisplay, y.UnderlyingTypeDisplay, StringComparison.Ordinal)
+ && x.Accessibility == y.Accessibility;
+ }
+
+ public int GetHashCode(StrongIdModel obj) => (obj.Namespace, obj.Name, obj.UnderlyingTypeDisplay, obj.Accessibility).GetHashCode();
+ }
+}
diff --git a/src/uis/AStar.Dev.Web/Components/Layout/NavMenu.razor b/src/uis/AStar.Dev.Web/Components/Layout/NavMenu.razor
index 0f5ea6e..348368f 100644
--- a/src/uis/AStar.Dev.Web/Components/Layout/NavMenu.razor
+++ b/src/uis/AStar.Dev.Web/Components/Layout/NavMenu.razor
@@ -9,48 +9,47 @@
diff --git a/src/uis/AStar.Dev.Web/Components/Pages/Admin/FileClassifications.razor b/src/uis/AStar.Dev.Web/Components/Pages/Admin/FileClassifications.razor
index a07646e..a81a84f 100644
--- a/src/uis/AStar.Dev.Web/Components/Pages/Admin/FileClassifications.razor
+++ b/src/uis/AStar.Dev.Web/Components/Pages/Admin/FileClassifications.razor
@@ -1,4 +1,206 @@
@page "/admin/file-classifications"
+@attribute [AllowAnonymous]
+@using FluentUI.Demo.Shared
+@using FluentUI.Demo.Shared.SampleData
+@using Microsoft.FluentUI.AspNetCore.Components
+@using App = AStar.Dev.Web.Components.App
+
+@inject HttpClient Http
+
+@inject NavigationManager NavManager
+
@App.PageTitle("File Classifications")
File Classifications
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nothing to see here. Carry on!
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+Simulate data loading
+
+
+
+
+
+
+
+
+
+
+
+ Clear
+
+
+ Search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+
+ FluentDataGrid _dataGrid = null!;
+ IQueryable _foodRecallItems = null!;
+ bool _loading = true;
+ readonly PaginationState _pagination2 = new PaginationState { ItemsPerPage = 10 };
+ string? _stateFilter = "NY";
+
+ protected async Task RefreshItemsAsync(GridItemsProviderRequest req)
+ {
+ _loading = true;
+ await InvokeAsync(StateHasChanged);
+
+ var filters = new Dictionary
+ {
+ { "skip", req.StartIndex },
+ { "limit", req.Count },
+ };
+
+ if (!string.IsNullOrWhiteSpace(_stateFilter))
+ filters.Add("search", $"state:{_stateFilter}");
+
+ var s = req.GetSortByProperties().FirstOrDefault();
+ if (req.SortByColumn != null && !string.IsNullOrEmpty(s.PropertyName))
+ {
+ filters.Add("sort", s.PropertyName + (s.Direction == SortDirection.Ascending ? ":asc" : ":desc"));
+ }
+
+ var url = NavManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", filters);
+
+ var response = await Http.GetFromJsonAsync(url);
+
+ _foodRecallItems = response!.Results.AsQueryable();
+ await _pagination2.SetTotalItemCountAsync(response!.Meta.Results.Total);
+
+ _loading = false;
+ await InvokeAsync(StateHasChanged);
+
+ }
+
+ public void ClearFilters()
+ {
+ _stateFilter = null;
+ }
+
+ public async Task DataGridRefreshDataAsync()
+ {
+ await _dataGrid.RefreshDataAsync(true);
+ }
+ readonly PaginationState _pagination = new () { ItemsPerPage = 4 };
+
+ record Person(int PersonId, string Name, DateOnly BirthDate);
+
+ readonly IQueryable _people = new[]
+ {
+ new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
+ new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
+ new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
+ new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
+ new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
+ new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
+ }.AsQueryable();
+
+ private readonly RenderFragment _template = @;
+
+ FluentDataGrid? _grid;
+ FluentSwitch? _clearToggle;
+
+ bool _clearItems = false;
+ public record SampleGridData(string Item1, string Item2, string Item3, string Item4);
+
+ IQueryable? _items = Enumerable.Empty().AsQueryable();
+
+ private IQueryable GenerateSampleGridData(int size)
+ {
+ SampleGridData[] data = new SampleGridData[size];
+
+ for (int i = 0; i < size; i++)
+ {
+ data[i] = new SampleGridData($"value {i}-1", $"value {i}-2", $"value {i}-3", $"value {i}-4");
+ }
+ return data.AsQueryable();
+ }
+ protected override void OnInitialized()
+ {
+ _items = GenerateSampleGridData(5000);
+ }
+
+ private void ToggleItems()
+ {
+ _items = _clearItems ? null : GenerateSampleGridData(5000);
+ }
+
+ private async Task SimulateDataLoading()
+ {
+ _clearItems = false;
+
+ _items = null;
+ _grid?.SetLoadingState(true);
+
+ await Task.Delay(1500);
+
+ _items = GenerateSampleGridData(5000);
+ _grid?.SetLoadingState(false);
+ }
+}
diff --git a/src/uis/AStar.Dev.Web/Components/Pages/Shared/Search.razor.cs b/src/uis/AStar.Dev.Web/Components/Pages/Shared/Search.razor.cs
index 249553f..0c93687 100644
--- a/src/uis/AStar.Dev.Web/Components/Pages/Shared/Search.razor.cs
+++ b/src/uis/AStar.Dev.Web/Components/Pages/Shared/Search.razor.cs
@@ -50,7 +50,7 @@ protected override async Task OnInitializedAsync()
{
FileClassifications =
[
- new FileClassification(new Dev.Files.Classifications.Api.FileClassification(Guid.Empty, 1, null, "-- Select (Optional) --", false, false)
+ new FileClassification(new Dev.Files.Classifications.Api.FileClassification(Guid.Empty, 1, "-- Select (Optional) --", null,null, false, false)
)
];
diff --git a/src/uis/AStar.Dev.Web/Models/FileClassification.cs b/src/uis/AStar.Dev.Web/Models/FileClassification.cs
index f82b10d..9279649 100644
--- a/src/uis/AStar.Dev.Web/Models/FileClassification.cs
+++ b/src/uis/AStar.Dev.Web/Models/FileClassification.cs
@@ -21,11 +21,11 @@ public FileClassification(Files.Classifications.Api.FileClassification fc)
/// Gets or sets the unique identifier for the file classification.
/// This property serves as the primary key for the entity.
///
- public Guid Id { get; set; }
+ public Guid? Id { get; set; }
///
///
- public int SearchLevel { get; set; }
+ public int? SearchLevel { get; set; }
///
///
@@ -36,18 +36,18 @@ public FileClassification(Files.Classifications.Api.FileClassification fc)
/// This property represents the descriptive label for a specific classification
/// and is often used to identify or categorize files within the database.
///
- public string Name { get; set; } = string.Empty;
+ public string? Name { get; set; }
///
/// Gets or sets a value indicating whether the file classification is considered a "Celebrity."
/// This property is used to mark specific classifications with special significance.
///
- public bool Celebrity { get; set; }
+ public bool? Celebrity { get; set; }
///
/// Gets or sets a value indicating whether this classification should be included in search results.
/// This property determines if files associated with this classification are considered searchable.
///
- public bool IncludeInSearch { get; set; }
+ public bool? IncludeInSearch { get; set; }
}
diff --git a/test/modules/apis/AStar.Dev.Files.Classifications.Api.Tests.Unit/Endpoints/FileClassifications/V1/GetFileClassificationsResponseTests.cs b/test/modules/apis/AStar.Dev.Files.Classifications.Api.Tests.Unit/Endpoints/FileClassifications/V1/GetFileClassificationsResponseTests.cs
index 380f52b..e10b796 100644
--- a/test/modules/apis/AStar.Dev.Files.Classifications.Api.Tests.Unit/Endpoints/FileClassifications/V1/GetFileClassificationsResponseTests.cs
+++ b/test/modules/apis/AStar.Dev.Files.Classifications.Api.Tests.Unit/Endpoints/FileClassifications/V1/GetFileClassificationsResponseTests.cs
@@ -21,14 +21,16 @@ public void Construction_Should_Set_Values_Correctly()
public void Deconstruction_Should_Work_As_Expected()
{
var id = Guid.CreateVersion7();
- var dto = new GetFileClassificationsResponse(id, "Alpha", false, true);
+ var dto = new GetFileClassificationsResponse(id, "Alpha", true, true);
- var (deId, deName, deInclude, deCeleb) = dto;
+ var (deId, deName, deInclude, deCeleb, deParentId, deSearchLevel) = dto;
deId.ShouldBe(id);
deName.ShouldBe("Alpha");
- deInclude.ShouldBeFalse();
+ deInclude.ShouldBeTrue();
deCeleb.ShouldBeTrue();
+ deParentId.ShouldBeNull();
+ deSearchLevel.ShouldBe(2);
}
[Fact]