feat(data): rename *.Services projects, lift TrackEntity onto BlazorBlocks data layer, regenerate initial Postgres migration
DeepDrftWeb.Services → DeepDrftData; DeepDrftContent.Services → DeepDrftContent.Data. TrackEntity:BaseEntity, TrackRepository:Repository<>, TrackManager:Manager<>+ITrackService. Drops DeepDrftModels PagingParameters/PagedResult in favour of Models.Common.* from BlazorBlocks. InitialCreate migration captures full schema including is_deleted index.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
using System.Text.Json;
|
||||
using DeepDrftContent.Data.FileDatabase.Models;
|
||||
|
||||
namespace DeepDrftContent.Data.FileDatabase.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for file I/O operations, matching the TypeScript file utilities
|
||||
/// </summary>
|
||||
public static class FileUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a file and returns it as a FileBinary object
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Path to the media file</param>
|
||||
/// <returns>FileBinary containing the file data</returns>
|
||||
public static async Task<FileBinary> FetchFileAsync(string mediaPath)
|
||||
{
|
||||
using var fileStream = new FileStream(mediaPath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 64 * 1024);
|
||||
|
||||
var buffer = new byte[fileStream.Length];
|
||||
var totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < fileStream.Length)
|
||||
{
|
||||
var bytesRead = await fileStream.ReadAsync(buffer.AsMemory(totalBytesRead));
|
||||
if (bytesRead == 0)
|
||||
throw new EndOfStreamException($"Unexpected end of stream while reading {mediaPath}");
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
return new FileBinary(new FileBinaryParams(buffer, buffer.Length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes binary data to a file
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Path where to write the file</param>
|
||||
/// <param name="buffer">Binary data to write</param>
|
||||
public static async Task PutFileAsync(string mediaPath, byte[] buffer)
|
||||
{
|
||||
const int chunkSize = 64 * 1024;
|
||||
|
||||
using var fileStream = new FileStream(mediaPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: chunkSize);
|
||||
|
||||
for (int offset = 0; offset < buffer.Length; offset += chunkSize)
|
||||
{
|
||||
var length = Math.Min(chunkSize, buffer.Length - offset);
|
||||
await fileStream.WriteAsync(buffer.AsMemory(offset, length));
|
||||
}
|
||||
|
||||
await fileStream.FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an object to a file using JSON
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the file</param>
|
||||
/// <param name="obj">Object to serialize</param>
|
||||
public static async Task PutObjectAsync<T>(string filePath, T obj)
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(obj, options);
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes an object from a JSON file
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the file</param>
|
||||
/// <returns>Deserialized object</returns>
|
||||
public static async Task<T> FetchObjectAsync<T>(string filePath)
|
||||
{
|
||||
var json = await File.ReadAllTextAsync(filePath);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var result = JsonSerializer.Deserialize<T>(json, options);
|
||||
return result ?? throw new InvalidOperationException($"Failed to deserialize object from {filePath}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a directory if it doesn't exist
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">Path to the directory</param>
|
||||
public static Task MakeVaultDirectoryAsync(string directoryPath)
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a file exists
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to check</param>
|
||||
/// <returns>True if file exists</returns>
|
||||
public static bool FileExists(string filePath)
|
||||
{
|
||||
return File.Exists(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a directory exists
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">Path to check</param>
|
||||
/// <returns>True if directory exists</returns>
|
||||
public static bool DirectoryExists(string directoryPath)
|
||||
{
|
||||
return Directory.Exists(directoryPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DeepDrftContent.Data.FileDatabase.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// A map implementation that uses structural equality for keys by serializing them to JSON.
|
||||
/// This provides the same behavior as the TypeScript StructuralMap.
|
||||
/// Optimized with caching to avoid repeated serialization.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type</typeparam>
|
||||
/// <typeparam name="TValue">The value type</typeparam>
|
||||
public class StructuralMap<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Dictionary<string, KeyValuePair<TKey, TValue>> _innerMap = new();
|
||||
private readonly Dictionary<TKey, string> _keyStringCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Converts a key to its string representation for structural comparison
|
||||
/// Uses caching to avoid expensive serialization on repeated lookups
|
||||
/// </summary>
|
||||
private string GetKeyString(TKey key)
|
||||
{
|
||||
if (key == null) return "null";
|
||||
|
||||
// For reference types, use cache to avoid repeated serialization
|
||||
if (!typeof(TKey).IsValueType && _keyStringCache.TryGetValue(key, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var keyString = key switch
|
||||
{
|
||||
string s => s,
|
||||
int or long or float or double or decimal => key.ToString()!,
|
||||
_ => JsonSerializer.Serialize(key)
|
||||
};
|
||||
|
||||
// Cache for reference types only (value types are cheap to convert)
|
||||
if (!typeof(TKey).IsValueType)
|
||||
{
|
||||
_keyStringCache[key] = keyString;
|
||||
}
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a key-value pair in the map
|
||||
/// </summary>
|
||||
public StructuralMap<TKey, TValue> Set(TKey key, TValue value)
|
||||
{
|
||||
var keyString = GetKeyString(key);
|
||||
_innerMap[keyString] = new KeyValuePair<TKey, TValue>(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value by key, or default if not found
|
||||
/// </summary>
|
||||
public TValue? Get(TKey key)
|
||||
{
|
||||
var keyString = GetKeyString(key);
|
||||
return _innerMap.TryGetValue(keyString, out var pair) ? pair.Value : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the map contains the specified key
|
||||
/// </summary>
|
||||
public bool Has(TKey key)
|
||||
{
|
||||
var keyString = GetKeyString(key);
|
||||
return _innerMap.ContainsKey(keyString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a key-value pair from the map
|
||||
/// </summary>
|
||||
public bool Delete(TKey key)
|
||||
{
|
||||
var keyString = GetKeyString(key);
|
||||
return _innerMap.Remove(keyString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all entries from the map
|
||||
/// </summary>
|
||||
public void Clear() => _innerMap.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of entries in the map
|
||||
/// </summary>
|
||||
public int Size => _innerMap.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all key-value pairs
|
||||
/// </summary>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return _innerMap.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all keys in the map
|
||||
/// </summary>
|
||||
public IEnumerable<TKey> Keys => _innerMap.Values.Select(pair => pair.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all values in the map
|
||||
/// </summary>
|
||||
public IEnumerable<TValue> Values => _innerMap.Values.Select(pair => pair.Value);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a callback for each key-value pair
|
||||
/// </summary>
|
||||
public void ForEach(Action<TValue, TKey, StructuralMap<TKey, TValue>> callback)
|
||||
{
|
||||
foreach (var (key, value) in this)
|
||||
{
|
||||
callback(value, key, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DeepDrftContent.Data.FileDatabase.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// A set implementation that uses structural equality for values by serializing them to JSON.
|
||||
/// This provides the same behavior as the TypeScript StructuralSet.
|
||||
/// Optimized with caching to avoid repeated serialization.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type</typeparam>
|
||||
public class StructuralSet<T> : IEnumerable<T> where T : notnull
|
||||
{
|
||||
private readonly Dictionary<string, T> _innerMap = new();
|
||||
private readonly Dictionary<T, string> _valueStringCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Converts a value to its string representation for structural comparison
|
||||
/// Uses caching to avoid expensive serialization on repeated lookups
|
||||
/// </summary>
|
||||
private string GetValueString(T value)
|
||||
{
|
||||
if (value == null) return "null";
|
||||
|
||||
// For reference types, use cache to avoid repeated serialization
|
||||
if (!typeof(T).IsValueType && _valueStringCache.TryGetValue(value, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var valueString = value switch
|
||||
{
|
||||
string s => s,
|
||||
int or long or float or double or decimal => value.ToString()!,
|
||||
_ => JsonSerializer.Serialize(value)
|
||||
};
|
||||
|
||||
// Cache for reference types only (value types are cheap to convert)
|
||||
if (!typeof(T).IsValueType)
|
||||
{
|
||||
_valueStringCache[value] = valueString;
|
||||
}
|
||||
|
||||
return valueString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a value to the set
|
||||
/// </summary>
|
||||
public StructuralSet<T> Add(T value)
|
||||
{
|
||||
var valueString = GetValueString(value);
|
||||
if (!_innerMap.ContainsKey(valueString))
|
||||
{
|
||||
_innerMap[valueString] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the set contains the specified value
|
||||
/// </summary>
|
||||
public bool Has(T value)
|
||||
{
|
||||
var valueString = GetValueString(value);
|
||||
return _innerMap.ContainsKey(valueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value from the set
|
||||
/// </summary>
|
||||
public bool Delete(T value)
|
||||
{
|
||||
var valueString = GetValueString(value);
|
||||
return _innerMap.Remove(valueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all values from the set
|
||||
/// </summary>
|
||||
public void Clear() => _innerMap.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of values in the set
|
||||
/// </summary>
|
||||
public int Size => _innerMap.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all values in the set
|
||||
/// </summary>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _innerMap.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all values in the set
|
||||
/// </summary>
|
||||
public IEnumerable<T> Values => _innerMap.Values;
|
||||
}
|
||||
Reference in New Issue
Block a user