78 lines
2.8 KiB
C#
78 lines
2.8 KiB
C#
using System.Xml.Serialization;
|
|
using ControlPlane.Core.Models;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ControlPlane.Core.Services;
|
|
|
|
/// <summary>
|
|
/// Reads and writes per-tenant XML config files under the ClientAssets folder.
|
|
/// One file per tenant: {subdomain}.xml
|
|
/// Thread-safe for concurrent reads; writes are serialized per subdomain via per-file locking.
|
|
/// </summary>
|
|
public class TenantRegistryService
|
|
{
|
|
private readonly string _folder;
|
|
private readonly ILogger<TenantRegistryService> _logger;
|
|
private static readonly XmlSerializer Serializer = new(typeof(TenantRecord));
|
|
|
|
// One lock object per subdomain so writes to different tenants never block each other
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, object> _locks = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public TenantRegistryService(IConfiguration configuration, ILogger<TenantRegistryService> logger)
|
|
{
|
|
_folder = configuration["ClientAssets__Folder"] ?? configuration["ClientAssets:Folder"]
|
|
?? Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "ClientAssets"));
|
|
_logger = logger;
|
|
Directory.CreateDirectory(_folder);
|
|
}
|
|
|
|
// -- Write --
|
|
|
|
public void Save(TenantRecord record)
|
|
{
|
|
var path = FilePath(record.Subdomain);
|
|
var gate = _locks.GetOrAdd(record.Subdomain, _ => new object());
|
|
lock (gate)
|
|
{
|
|
using var writer = new StreamWriter(path, append: false, System.Text.Encoding.UTF8);
|
|
Serializer.Serialize(writer, record);
|
|
}
|
|
_logger.LogInformation("Saved tenant record: {Path}", path);
|
|
}
|
|
|
|
// -- Read --
|
|
|
|
public TenantRecord? TryGet(string subdomain)
|
|
{
|
|
var path = FilePath(subdomain);
|
|
if (!File.Exists(path)) return null;
|
|
using var reader = new StreamReader(path, System.Text.Encoding.UTF8);
|
|
return (TenantRecord?)Serializer.Deserialize(reader);
|
|
}
|
|
|
|
public IReadOnlyList<TenantRecord> GetAll()
|
|
{
|
|
var results = new List<TenantRecord>();
|
|
foreach (var file in Directory.EnumerateFiles(_folder, "*.xml"))
|
|
{
|
|
try
|
|
{
|
|
using var reader = new StreamReader(file, System.Text.Encoding.UTF8);
|
|
if (Serializer.Deserialize(reader) is TenantRecord record)
|
|
results.Add(record);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Skipping malformed tenant file: {File}", file);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public bool Exists(string subdomain) => File.Exists(FilePath(subdomain));
|
|
|
|
private string FilePath(string subdomain) =>
|
|
Path.Combine(_folder, $"{subdomain.ToLowerInvariant()}.xml");
|
|
}
|