Initial commit
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
using RobotNet10.CustomConfiguration.Events;
|
||||
using RobotNet10.CustomConfiguration.Helpers;
|
||||
using RobotNet10.CustomConfiguration.Models;
|
||||
using RobotNet10.CustomConfiguration.Validators;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RobotNet10.CustomConfiguration.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation của các methods trong ConfigService
|
||||
/// </summary>
|
||||
public partial class ConfigService
|
||||
{
|
||||
public async Task<ConfigFile> CreateConfigAsync(string configType, List<ConfigVariable> variables, string? description = null)
|
||||
{
|
||||
// Validate ConfigType
|
||||
if (string.IsNullOrWhiteSpace(configType))
|
||||
{
|
||||
throw new ArgumentException("ConfigType cannot be empty", nameof(configType));
|
||||
}
|
||||
|
||||
// Check if ConfigType already exists
|
||||
var existing = await GetMetadataByTypeAsync(configType);
|
||||
if (existing != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Config with type '{configType}' already exists");
|
||||
}
|
||||
|
||||
// Create config file
|
||||
var config = new ConfigFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ConfigType = configType,
|
||||
Variables = variables,
|
||||
Description = description,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
FilePath = $"{ConfigPath}/{configType}{ConfigFileExtension}"
|
||||
};
|
||||
|
||||
// Validate config
|
||||
var validationResult = ConfigValidator.ValidateConfig(config);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
|
||||
}
|
||||
|
||||
// Save config file (includes metadata)
|
||||
await SaveConfigFileAsync(config);
|
||||
|
||||
// Trigger event
|
||||
OnConfigChanged(config, ConfigChangeType.Created);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public async Task<ConfigFile?> GetConfigByIdAsync(Guid id)
|
||||
{
|
||||
// Load all configs and find by ID
|
||||
var allMetadata = await LoadAllMetadataAsync();
|
||||
var metadata = allMetadata.FirstOrDefault(m => m.Id == id);
|
||||
if (metadata == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await LoadConfigFileAsync(metadata.ConfigType);
|
||||
}
|
||||
|
||||
public async Task<ConfigFile?> GetConfigByTypeAsync(string configType)
|
||||
{
|
||||
return await LoadConfigFileAsync(configType);
|
||||
}
|
||||
|
||||
public async Task<List<ConfigFileMetadata>> GetAllConfigsAsync()
|
||||
{
|
||||
var metadata = await LoadAllMetadataAsync();
|
||||
return [.. metadata.OrderBy(m => m.ConfigType)];
|
||||
}
|
||||
|
||||
public async Task<List<ConfigFileMetadata>> SearchConfigsAsync(string? searchText)
|
||||
{
|
||||
var allMetadata = await GetAllConfigsAsync();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
return allMetadata;
|
||||
}
|
||||
|
||||
var searchLower = searchText.ToLower();
|
||||
return [.. allMetadata.Where(m =>
|
||||
m.ConfigType.ToLower().Contains(searchLower) ||
|
||||
(m.Description != null && m.Description.ToLower().Contains(searchLower))
|
||||
)];
|
||||
}
|
||||
|
||||
public async Task<ConfigFile> UpdateConfigAsync(Guid id, List<ConfigVariable>? variables = null, string? description = null)
|
||||
{
|
||||
var config = await GetConfigByIdAsync(id) ?? throw new KeyNotFoundException($"Config with ID '{id}' not found");
|
||||
|
||||
// Update variables if provided
|
||||
if (variables != null)
|
||||
{
|
||||
config.Variables = variables;
|
||||
}
|
||||
|
||||
// Update description if provided
|
||||
if (description != null)
|
||||
{
|
||||
config.Description = description;
|
||||
}
|
||||
|
||||
config.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Validate config
|
||||
var validationResult = ConfigValidator.ValidateConfig(config);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
|
||||
}
|
||||
|
||||
// Save config file (includes metadata)
|
||||
await SaveConfigFileAsync(config);
|
||||
|
||||
// Trigger event
|
||||
OnConfigChanged(config, ConfigChangeType.Updated);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteConfigAsync(Guid id)
|
||||
{
|
||||
// Find config by ID
|
||||
var allMetadata = await LoadAllMetadataAsync();
|
||||
var metadata = allMetadata.FirstOrDefault(m => m.Id == id);
|
||||
if (metadata == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete config file
|
||||
// objectName should be {configType}.config so StorageManager adds .json to make {configType}.config.json
|
||||
var objectName = $"{metadata.ConfigType}.config";
|
||||
var exists = await _storageManager.ExistsAsync(ConfigPath, objectName, CancellationToken.None);
|
||||
if (exists)
|
||||
{
|
||||
await _storageManager.DeleteAsync(ConfigPath, objectName, CancellationToken.None);
|
||||
|
||||
// Trigger event
|
||||
var deletedConfig = new ConfigFile
|
||||
{
|
||||
Id = metadata.Id,
|
||||
ConfigType = metadata.ConfigType,
|
||||
CreatedAt = metadata.CreatedAt,
|
||||
UpdatedAt = metadata.UpdatedAt,
|
||||
Description = metadata.Description
|
||||
};
|
||||
OnConfigChanged(deletedConfig, ConfigChangeType.Deleted);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ConfigTypeExistsAsync(string configType)
|
||||
{
|
||||
var metadata = await GetMetadataByTypeAsync(configType);
|
||||
return metadata != null;
|
||||
}
|
||||
|
||||
public async Task<ConfigFile> ImportConfigFromJsonAsync(Stream jsonStream, string configType)
|
||||
{
|
||||
return await ImportConfigFromJsonAsync(jsonStream, configType, null);
|
||||
}
|
||||
|
||||
public async Task<ConfigFile> ImportConfigFromJsonAsync(Stream jsonStream, string configType, string? description)
|
||||
{
|
||||
// Validate ConfigType
|
||||
if (string.IsNullOrWhiteSpace(configType))
|
||||
{
|
||||
throw new ArgumentException("ConfigType cannot be empty", nameof(configType));
|
||||
}
|
||||
|
||||
// Check if ConfigType already exists
|
||||
var existing = await GetMetadataByTypeAsync(configType);
|
||||
if (existing != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Config with type '{configType}' already exists");
|
||||
}
|
||||
|
||||
// Try to parse as full config file (new format with metadata)
|
||||
// Reset stream position first
|
||||
jsonStream.Position = 0;
|
||||
string? fileDescription = null;
|
||||
List<ConfigVariable> variables;
|
||||
|
||||
try
|
||||
{
|
||||
// Try to parse as ConfigFile (new format)
|
||||
var configFile = JsonConfigParser.ParseConfigFile(jsonStream);
|
||||
// If successful, use description from file
|
||||
fileDescription = configFile.Description;
|
||||
variables = configFile.Variables;
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or ArgumentException)
|
||||
{
|
||||
// If parsing as ConfigFile fails, try old format (array of variables)
|
||||
jsonStream.Position = 0;
|
||||
variables = JsonConfigParser.ParseVariables(jsonStream);
|
||||
}
|
||||
|
||||
// Use description from parameter if provided, otherwise use description from file
|
||||
var finalDescription = !string.IsNullOrWhiteSpace(description) ? description : fileDescription;
|
||||
|
||||
// Create config with description (from parameter or file)
|
||||
var config = await CreateConfigAsync(configType, variables, finalDescription);
|
||||
|
||||
// Note: CreateConfigAsync already triggers Created event, so no need to trigger again
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public async Task<Stream> ExportConfigToJsonAsync(Guid id)
|
||||
{
|
||||
var config = await GetConfigByIdAsync(id);
|
||||
return config == null ? throw new KeyNotFoundException($"Config with ID '{id}' not found") : await ExportConfigToJsonAsync(config);
|
||||
}
|
||||
|
||||
public Task<Stream> ExportConfigToJsonAsync(ConfigFile config)
|
||||
{
|
||||
// Export với format mới (metadata + variables)
|
||||
var json = JsonConfigParser.SerializeConfigFile(config);
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
Stream stream = new MemoryStream(bytes)
|
||||
{
|
||||
Position = 0 // Ensure stream is at the beginning
|
||||
};
|
||||
return Task.FromResult(stream);
|
||||
}
|
||||
|
||||
public async Task<ConfigFile> UpdateVariableAsync(Guid configId, string variableName, object? value)
|
||||
{
|
||||
var config = await GetConfigByIdAsync(configId) ??
|
||||
throw new KeyNotFoundException($"Config with ID '{configId}' not found");
|
||||
var variable = config.Variables.FirstOrDefault(v => v.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase)) ??
|
||||
throw new KeyNotFoundException($"Variable '{variableName}' not found in config");
|
||||
variable.Value = value;
|
||||
config.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Validate
|
||||
var validationResult = ConfigValidator.ValidateConfig(config);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
|
||||
}
|
||||
|
||||
// Save config file (includes metadata)
|
||||
await SaveConfigFileAsync(config);
|
||||
|
||||
// Trigger event
|
||||
OnConfigChanged(config, ConfigChangeType.VariableUpdated, variableName);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public async Task<ConfigFile> AddVariableAsync(Guid configId, ConfigVariable variable)
|
||||
{
|
||||
var config = await GetConfigByIdAsync(configId) ?? throw new KeyNotFoundException($"Config with ID '{configId}' not found");
|
||||
|
||||
// Check if variable name already exists
|
||||
if (config.Variables.Any(v => v.Name.Equals(variable.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
throw new InvalidOperationException($"Variable '{variable.Name}' already exists in config");
|
||||
}
|
||||
|
||||
config.Variables.Add(variable);
|
||||
config.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Validate
|
||||
var validationResult = ConfigValidator.ValidateConfig(config);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
|
||||
}
|
||||
|
||||
// Save config file (includes metadata)
|
||||
await SaveConfigFileAsync(config);
|
||||
|
||||
// Trigger event
|
||||
OnConfigChanged(config, ConfigChangeType.VariableAdded, variable.Name);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public async Task<ConfigFile> RemoveVariableAsync(Guid configId, string variableName)
|
||||
{
|
||||
var config = await GetConfigByIdAsync(configId) ??
|
||||
throw new KeyNotFoundException($"Config with ID '{configId}' not found");
|
||||
var variable = config.Variables.FirstOrDefault(v => v.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase)) ??
|
||||
throw new KeyNotFoundException($"Variable '{variableName}' not found in config");
|
||||
config.Variables.Remove(variable);
|
||||
config.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Validate
|
||||
var validationResult = ConfigValidator.ValidateConfig(config);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
|
||||
}
|
||||
|
||||
// Save config file (includes metadata)
|
||||
await SaveConfigFileAsync(config);
|
||||
|
||||
// Trigger event
|
||||
OnConfigChanged(config, ConfigChangeType.VariableRemoved, variableName);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PRIVATE HELPER METHODS
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// Load config file từ StorageManager (format mới với metadata)
|
||||
/// </summary>
|
||||
private async Task<ConfigFile?> LoadConfigFileAsync(string configType)
|
||||
{
|
||||
try
|
||||
{
|
||||
// objectName should be {configType}.config so StorageManager finds {configType}.config.json
|
||||
var objectName = $"{configType}.config";
|
||||
var exists = await _storageManager.ExistsAsync(ConfigPath, objectName, CancellationToken.None);
|
||||
if (!exists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get file stream from StorageManager (works with both local and remote storage)
|
||||
using var stream = await _storageManager.GetFileAsync(ConfigPath, objectName, CancellationToken.None);
|
||||
|
||||
// Parse config file directly from stream (format mới với metadata)
|
||||
return JsonConfigParser.ParseConfigFile(stream);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Config file corrupted/invalid format - treat as not found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save config file vào StorageManager (format mới với metadata)
|
||||
/// </summary>
|
||||
private async Task SaveConfigFileAsync(ConfigFile config)
|
||||
{
|
||||
// Serialize với format mới (metadata + variables)
|
||||
var json = JsonConfigParser.SerializeConfigFile(config);
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
// objectName should be {configType}.config so StorageManager adds .json to make {configType}.config.json
|
||||
var objectName = $"{config.ConfigType}.config";
|
||||
|
||||
using var stream = new MemoryStream(bytes);
|
||||
await _storageManager.UploadAsync(
|
||||
ConfigPath,
|
||||
objectName,
|
||||
stream,
|
||||
stream.Length,
|
||||
"application/json",
|
||||
CancellationToken.None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user