From 3429c8f8dc75cedcc179fba4ec495a5c104505be Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 31 Dec 2024 18:31:21 +0000 Subject: [PATCH] Added database table data into configuration. Store saves is auto-handled. Added Action & Redemption stores. --- Models/Voice.cs | 8 --- Requests/CreateRedeemableAction.cs | 53 ++++++++++++++ Requests/CreateRedemption.cs | 47 ++++++++++++ Requests/CreateTTSVoice.cs | 5 +- Requests/DeletePolicy.cs | 2 +- Requests/DeleteRedeemableAction.cs | 32 +++++++++ Requests/DeleteRedemption.cs | 32 +++++++++ Requests/DeleteTTSFilter.cs | 12 +++- Requests/DeleteTTSVoice.cs | 16 +++-- Requests/GetTTSVoices.cs | 4 +- Requests/UpdateRedeemableAction.cs | 59 +++++++++++++++ Requests/UpdateRedemption.cs | 62 ++++++++++++++++ Requests/UpdateTTSVoice.cs | 5 +- ServerConfiguration.cs | 3 + Services/ChannelManager.cs | 27 ++++--- Startup.cs | 21 +++++- Store/ActionStore.cs | 53 ++++++++++++++ Store/ChatterStore.cs | 65 ++--------------- Store/IStore.cs | 2 +- Store/Internal/DatabaseTable.cs | 11 +++ Store/{ => Internal}/GroupSaveSqlGenerator.cs | 57 ++++++++++----- Store/{ => Internal}/GroupedSaveStore.cs | 12 ++-- Store/Internal/SelfGeneratedStore.cs | 61 ++++++++++++++++ Store/PolicyStore.cs | 68 ++---------------- Store/RedemptionStore.cs | 54 ++++++++++++++ Store/TTSFilterStore.cs | 66 ++--------------- Store/UserStore.cs | 67 ++--------------- Store/VoiceStateStore.cs | 55 ++++++++++++++ Store/VoiceStore.cs | 72 +++---------------- 29 files changed, 657 insertions(+), 374 deletions(-) delete mode 100644 Models/Voice.cs create mode 100644 Requests/CreateRedeemableAction.cs create mode 100644 Requests/CreateRedemption.cs create mode 100644 Requests/DeleteRedeemableAction.cs create mode 100644 Requests/DeleteRedemption.cs create mode 100644 Requests/UpdateRedeemableAction.cs create mode 100644 Requests/UpdateRedemption.cs create mode 100644 Store/ActionStore.cs create mode 100644 Store/Internal/DatabaseTable.cs rename Store/{ => Internal}/GroupSaveSqlGenerator.cs (81%) rename Store/{ => Internal}/GroupedSaveStore.cs (95%) create mode 100644 Store/Internal/SelfGeneratedStore.cs create mode 100644 Store/RedemptionStore.cs create mode 100644 Store/VoiceStateStore.cs diff --git a/Models/Voice.cs b/Models/Voice.cs deleted file mode 100644 index 66ee2c2..0000000 --- a/Models/Voice.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HermesSocketServer.Models -{ - public class Voice - { - public required string Id { get; set; } - public required string Name { get; set; } - } -} \ No newline at end of file diff --git a/Requests/CreateRedeemableAction.cs b/Requests/CreateRedeemableAction.cs new file mode 100644 index 0000000..9e6aa9b --- /dev/null +++ b/Requests/CreateRedeemableAction.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Models; +using ILogger = Serilog.ILogger; + +namespace HermesSocketServer.Requests +{ + public class CreateRedeemableAction : IRequest + { + public string Name => "create_redeemable_action"; + public string[] RequiredKeys => ["name", "data", "type"]; + private ILogger _logger; + + public CreateRedeemableAction(ILogger logger) + { + _logger = logger; + } + + public Task Grant(Channel channel, IDictionary data) + { + string name = data["name"].ToString()!; + string d = data["data"].ToString()!; + string type = data["type"].ToString()!; + IDictionary dict = new Dictionary(); + + try + { + dict = JsonSerializer.Deserialize>(d)!; + } + catch (Exception ex) + { + _logger.Error(ex, $"Failed to parse data on redeemable action while creating action [name: {name}][type: {type}][data: {d}]"); + return Task.FromResult(RequestResult.Failed("Could not parse the data on this action.")); + } + + var action = new RedeemableAction() + { + UserId = channel.Id, + Name = name, + Data = dict, + Type = type, + }; + + bool result = channel.Actions.Set(name, action); + if (result) + { + _logger.Information($"Added redeemable action to channel [name: {name}][type: {type}][channel: {channel.Id}]"); + return Task.FromResult(RequestResult.Successful(action)); + } + return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache.")); + } + } +} \ No newline at end of file diff --git a/Requests/CreateRedemption.cs b/Requests/CreateRedemption.cs new file mode 100644 index 0000000..14b86a1 --- /dev/null +++ b/Requests/CreateRedemption.cs @@ -0,0 +1,47 @@ +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Models; +using ILogger = Serilog.ILogger; + +namespace HermesSocketServer.Requests +{ + public class CreateRedemption : IRequest + { + public string Name => "create_redemption"; + public string[] RequiredKeys => ["redemption", "action", "order"]; + private ILogger _logger; + + public CreateRedemption(ILogger logger) + { + _logger = logger; + } + + public Task Grant(Channel channel, IDictionary data) + { + var id = Guid.NewGuid(); + string redemptionId = data["redemption"].ToString()!; + string actionName = data["action"].ToString()!; + if (channel.Actions.Get(actionName) == null) + return Task.FromResult(RequestResult.Failed("Action Name must be an existing action.")); + if (!int.TryParse(data["order"].ToString()!, out var order)) + return Task.FromResult(RequestResult.Failed("Order must be an integer.")); + + var redemption = new Redemption() + { + Id = id.ToString(), + UserId = channel.Id, + RedemptionId = redemptionId, + ActionName = actionName, + Order = order, + State = true, + }; + + bool result = channel.Redemptions.Set(id.ToString(), redemption); + if (result) + { + _logger.Information($"Added redemption to channel [id: {id}][redemption id: {redemptionId}][action: {actionName}][order: {order}][channel: {channel.Id}]"); + return Task.FromResult(RequestResult.Successful(redemption)); + } + return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache.")); + } + } +} \ No newline at end of file diff --git a/Requests/CreateTTSVoice.cs b/Requests/CreateTTSVoice.cs index 3c3d8f7..4b49813 100644 --- a/Requests/CreateTTSVoice.cs +++ b/Requests/CreateTTSVoice.cs @@ -1,3 +1,4 @@ +using HermesSocketLibrary.Requests.Messages; using HermesSocketServer.Models; using HermesSocketServer.Store; using ILogger = Serilog.ILogger; @@ -8,7 +9,7 @@ namespace HermesSocketServer.Requests { public string Name => "create_tts_voice"; public string[] RequiredKeys => ["voice"]; - private IStore _voices; + private IStore _voices; private ILogger _logger; private Random _random; @@ -24,7 +25,7 @@ namespace HermesSocketServer.Requests string voice = data["voice"].ToString()!; string id = RandomString(25); - var result = _voices.Set(id, new Voice() + var result = _voices.Set(id, new TTSVoice() { Id = id, Name = voice diff --git a/Requests/DeletePolicy.cs b/Requests/DeletePolicy.cs index 0a117b7..7fec239 100644 --- a/Requests/DeletePolicy.cs +++ b/Requests/DeletePolicy.cs @@ -25,7 +25,7 @@ namespace HermesSocketServer.Requests } _logger.Warning($"Failed to find policy by id [id: {policyId}]"); - return Task.FromResult(RequestResult.Failed("Cannot find the policy by id.")); + return Task.FromResult(RequestResult.Failed("Policy ID does not exist.")); } } } \ No newline at end of file diff --git a/Requests/DeleteRedeemableAction.cs b/Requests/DeleteRedeemableAction.cs new file mode 100644 index 0000000..f6c2410 --- /dev/null +++ b/Requests/DeleteRedeemableAction.cs @@ -0,0 +1,32 @@ +using HermesSocketServer.Models; +using ILogger = Serilog.ILogger; + +namespace HermesSocketServer.Requests +{ + public class DeleteRedeemableAction : IRequest + { + public string Name => "delete_redeemable_action"; + public string[] RequiredKeys => ["name"]; + private ILogger _logger; + + public DeleteRedeemableAction(ILogger logger) + { + _logger = logger; + } + + public Task Grant(Channel channel, IDictionary data) + { + string name = data["name"].ToString()!; + var result = channel.Actions.Remove(name); + + if (result) + { + _logger.Information($"Deleted a redeemable action by name [name: {name}]"); + return Task.FromResult(RequestResult.Successful(null)); + } + + _logger.Warning($"Action name does not exist [id: {name}]"); + return Task.FromResult(RequestResult.Failed("Action name does not exist.")); + } + } +} \ No newline at end of file diff --git a/Requests/DeleteRedemption.cs b/Requests/DeleteRedemption.cs new file mode 100644 index 0000000..cb2ecd5 --- /dev/null +++ b/Requests/DeleteRedemption.cs @@ -0,0 +1,32 @@ +using HermesSocketServer.Models; +using ILogger = Serilog.ILogger; + +namespace HermesSocketServer.Requests +{ + public class DeleteRedemption : IRequest + { + public string Name => "delete_redemption"; + public string[] RequiredKeys => ["id"]; + private ILogger _logger; + + public DeleteRedemption(ILogger logger) + { + _logger = logger; + } + + public Task Grant(Channel channel, IDictionary data) + { + string id = data["id"].ToString()!; + var result = channel.Redemptions.Remove(id); + + if (result) + { + _logger.Information($"Deleted a redemption by id [id: {id}]"); + return Task.FromResult(RequestResult.Successful(null)); + } + + _logger.Warning($"Redemption ID does not exist [id: {id}]"); + return Task.FromResult(RequestResult.Failed("Redemption ID does not exist.")); + } + } +} \ No newline at end of file diff --git a/Requests/DeleteTTSFilter.cs b/Requests/DeleteTTSFilter.cs index 7ab5a0a..1685d76 100644 --- a/Requests/DeleteTTSFilter.cs +++ b/Requests/DeleteTTSFilter.cs @@ -17,10 +17,16 @@ namespace HermesSocketServer.Requests public Task Grant(Channel channel, IDictionary data) { string filterId = data["id"].ToString()!; - channel.Filters.Remove(filterId); + var result = channel.Filters.Remove(filterId); - _logger.Information($"Deleted a TTS filter by id [tts filter id: {filterId}]"); - return Task.FromResult(RequestResult.Successful(null)); + if (result) + { + _logger.Information($"Deleted a TTS filter by id [tts filter id: {filterId}]"); + return Task.FromResult(RequestResult.Successful(null)); + } + + _logger.Warning($"Filter ID does not exist [id: {filterId}]"); + return Task.FromResult(RequestResult.Failed("Filter ID does not exist.")); } } } \ No newline at end of file diff --git a/Requests/DeleteTTSVoice.cs b/Requests/DeleteTTSVoice.cs index dac030e..0142097 100644 --- a/Requests/DeleteTTSVoice.cs +++ b/Requests/DeleteTTSVoice.cs @@ -1,3 +1,4 @@ +using HermesSocketLibrary.Requests.Messages; using HermesSocketServer.Models; using HermesSocketServer.Store; using ILogger = Serilog.ILogger; @@ -8,7 +9,7 @@ namespace HermesSocketServer.Requests { public string Name => "delete_tts_voice"; public string[] RequiredKeys => ["voice"]; - private IStore _voices; + private IStore _voices; private ILogger _logger; public DeleteTTSVoice(VoiceStore voices, ILogger logger) @@ -20,9 +21,16 @@ namespace HermesSocketServer.Requests public Task Grant(Channel channel, IDictionary data) { string voiceId = data["voice"].ToString()!; - _voices.Remove(voiceId); - _logger.Information($"Deleted a voice by id [voice id: {voiceId}]"); - return Task.FromResult(RequestResult.Successful(null)); + var result = _voices.Remove(voiceId); + + if (result) + { + _logger.Information($"Deleted a voice by id [voice id: {voiceId}]"); + return Task.FromResult(RequestResult.Successful(null)); + } + + _logger.Warning($"Voice ID does not exist [id: {voiceId}]"); + return Task.FromResult(RequestResult.Failed("Voice ID does not exist.")); } } } \ No newline at end of file diff --git a/Requests/GetTTSVoices.cs b/Requests/GetTTSVoices.cs index 35059e3..9763438 100644 --- a/Requests/GetTTSVoices.cs +++ b/Requests/GetTTSVoices.cs @@ -20,10 +20,10 @@ namespace HermesSocketServer.Requests public Task Grant(Channel channel, IDictionary data) { - IEnumerable voices = _voices.Get().Select(v => new VoiceDetails() + IEnumerable voices = _voices.Get().Select(v => new TTSVoice() { Id = v.Value.Id, - Name = v.Value.Name + Name = v.Value.Name, }); _logger.Information($"Fetched all TTS voices for channel [channel: {channel.Id}]"); diff --git a/Requests/UpdateRedeemableAction.cs b/Requests/UpdateRedeemableAction.cs new file mode 100644 index 0000000..820db3a --- /dev/null +++ b/Requests/UpdateRedeemableAction.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Models; +using ILogger = Serilog.ILogger; + +namespace HermesSocketServer.Requests +{ + public class UpdateRedeemableAction : IRequest + { + public string Name => "update_redeemable_action"; + public string[] RequiredKeys => ["name", "data", "type"]; + private ILogger _logger; + + public UpdateRedeemableAction(ILogger logger) + { + _logger = logger; + } + + public Task Grant(Channel channel, IDictionary data) + { + string name = data["name"].ToString()!; + string d = data["data"].ToString()!; + string type = data["type"].ToString()!; + IDictionary dict = new Dictionary(); + + try + { + dict = JsonSerializer.Deserialize>(d)!; + } + catch (Exception ex) + { + _logger.Error(ex, $"Failed to parse data on redeemable action while updating action [name: {name}][type: {type}][data: {d}]"); + return Task.FromResult(RequestResult.Failed("Could not parse the data on this action.")); + } + + var action = new RedeemableAction() + { + UserId = channel.Id, + Name = name, + Data = dict, + Type = type, + }; + + bool result = channel.Actions.Modify(name, action => + { + action.Type = type; + action.Data = dict; + }); + if (result) + { + _logger.Information($"Added redeemable action to channel [name: {name}][type: {type}][channel: {channel.Id}]"); + return Task.FromResult(RequestResult.Successful(action)); + } + if (channel.Actions.Get(name) == null) + return Task.FromResult(RequestResult.Failed("Action does not exist.")); + return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache.")); + } + } +} \ No newline at end of file diff --git a/Requests/UpdateRedemption.cs b/Requests/UpdateRedemption.cs new file mode 100644 index 0000000..40b0335 --- /dev/null +++ b/Requests/UpdateRedemption.cs @@ -0,0 +1,62 @@ +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Models; +using ILogger = Serilog.ILogger; + +namespace HermesSocketServer.Requests +{ + public class UpdateRedemption : IRequest + { + public string Name => "update_redemption"; + public string[] RequiredKeys => ["id", "redemption", "action", "order"]; + private ILogger _logger; + + public UpdateRedemption(ILogger logger) + { + _logger = logger; + } + + public Task Grant(Channel channel, IDictionary data) + { + var id = data["id"].ToString()!; + string redemptionId = data["redemption"].ToString()!; + string actionName = data["action"].ToString()!; + if (channel.Actions.Get(actionName) == null) + return Task.FromResult(RequestResult.Failed("Action Name must be an existing action.")); + if (!int.TryParse(data["order"].ToString()!, out var order)) + return Task.FromResult(RequestResult.Failed("Order must be an integer.")); + bool state = data["state"].ToString()?.ToLower() == "true"; + + var redemption = new Redemption() + { + Id = id, + UserId = channel.Id, + RedemptionId = redemptionId, + ActionName = actionName, + Order = order, + State = true, + }; + + bool result = channel.Redemptions.Modify(id.ToString(), r => + { + if (r.UserId != channel.Id) + return; + + r.RedemptionId = redemptionId; + r.ActionName = actionName; + r.Order = order; + r.State = state; + }); + + var r = channel.Redemptions.Get(id); + if (result) + { + _logger.Information($"Added redemption to channel [id: {id}][redemption id: {redemptionId}][action: {actionName}][order: {order}][channel: {channel.Id}]"); + return Task.FromResult(RequestResult.Successful(r)); + } + + if (r == null || r.UserId != channel.Id) + return Task.FromResult(RequestResult.Failed("Redemption does not exist.")); + return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache.")); + } + } +} \ No newline at end of file diff --git a/Requests/UpdateTTSVoice.cs b/Requests/UpdateTTSVoice.cs index 9ce44c5..f9bce32 100644 --- a/Requests/UpdateTTSVoice.cs +++ b/Requests/UpdateTTSVoice.cs @@ -1,3 +1,4 @@ +using HermesSocketLibrary.Requests.Messages; using HermesSocketServer.Models; using HermesSocketServer.Store; using ILogger = Serilog.ILogger; @@ -8,7 +9,7 @@ namespace HermesSocketServer.Requests { public string Name => "update_tts_voice"; public string[] RequiredKeys => ["voice", "voiceId"]; - private IStore _voices; + private IStore _voices; private ILogger _logger; public UpdateTTSVoice(VoiceStore voices, ILogger logger) @@ -22,7 +23,7 @@ namespace HermesSocketServer.Requests string voiceName = data["voice"].ToString()!; string voiceId = data["voiceid"].ToString()!; - var result = _voices.Set(voiceId, new Voice() + var result = _voices.Set(voiceId, new TTSVoice() { Id = voiceId, Name = voiceName diff --git a/ServerConfiguration.cs b/ServerConfiguration.cs index f779e86..87b8126 100644 --- a/ServerConfiguration.cs +++ b/ServerConfiguration.cs @@ -1,3 +1,5 @@ +using HermesSocketServer.Store.Internal; + namespace HermesSocketServer { public class ServerConfiguration @@ -19,6 +21,7 @@ namespace HermesSocketServer { public required string ConnectionString; public int SaveDelayInSeconds; + public required IDictionary Tables; } public class TTSConfiguration diff --git a/Services/ChannelManager.cs b/Services/ChannelManager.cs index 5e47721..df71f14 100644 --- a/Services/ChannelManager.cs +++ b/Services/ChannelManager.cs @@ -9,14 +9,16 @@ namespace HermesSocketServer.Services { private readonly UserStore _users; private readonly Database _database; + private readonly ServerConfiguration _configuration; private readonly Serilog.ILogger _logger; private readonly IDictionary _channels; private readonly object _lock; - public ChannelManager(UserStore users, Database database, Serilog.ILogger logger) + public ChannelManager(UserStore users, Database database, ServerConfiguration configuration, Serilog.ILogger logger) { _users = users; _database = database; + _configuration = configuration; _logger = logger; _channels = new ConcurrentDictionary(); _lock = new object(); @@ -27,22 +29,25 @@ namespace HermesSocketServer.Services { var user = _users.Get(userId); if (user == null) - { return Task.FromResult(null); - } + lock (_lock) { if (_channels.ContainsKey(userId)) - { return Task.FromResult(null); - } - var chatters = new ChatterStore(userId, _database, _logger); - var policies = new PolicyStore(userId, _database, _logger); - var filters = new TTSFilterStore(userId, _database, _logger); - var actions = new ActionStore(userId, _database, _logger); - var redemptions = new RedemptionStore(userId, _database, _logger); - + var actionTable = _configuration.Database.Tables["Action"]; + var chatterTable = _configuration.Database.Tables["Chatter"]; + var policyTable = _configuration.Database.Tables["Policy"]; + var redemptionTable = _configuration.Database.Tables["Redemption"]; + var ttsFilterTable = _configuration.Database.Tables["TtsFilter"]; + + var chatters = new ChatterStore(userId, chatterTable, _database, _logger); + var policies = new PolicyStore(userId, policyTable, _database, _logger); + var filters = new TTSFilterStore(userId, ttsFilterTable, _database, _logger); + var actions = new ActionStore(userId, actionTable, _database, _logger); + var redemptions = new RedemptionStore(userId, redemptionTable, _database, _logger); + Task.WaitAll([ chatters.Load(), policies.Load(), diff --git a/Startup.cs b/Startup.cs index 3887c3d..907449a 100644 --- a/Startup.cs +++ b/Startup.cs @@ -15,6 +15,8 @@ using Microsoft.AspNetCore.Connections; using HermesSocketServer.Validators; using HermesSocketServer.Store; using HermesSocketServer.Services; +using HermesSocketServer.Store.Internal; +using Microsoft.Extensions.DependencyInjection; var yamlDeserializer = new DeserializerBuilder() @@ -78,15 +80,26 @@ s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); +// Database Tables +if (configuration.Database.Tables != null) +{ + foreach (var table in configuration.Database.Tables) + s.AddKeyedTransient(table.Key, (sp, _) => table.Value); +} + // Stores s.AddSingleton(); s.AddSingleton(); // Request handlers s.AddSingleton(); +s.AddSingleton(); +s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); +s.AddSingleton(); +s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); @@ -96,18 +109,20 @@ s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); -s.AddSingleton(); s.AddSingleton(); +s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); +s.AddSingleton(); +s.AddSingleton(); +s.AddSingleton(); +s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); -s.AddSingleton(); s.AddSingleton(); -s.AddSingleton(); // Managers s.AddSingleton(); diff --git a/Store/ActionStore.cs b/Store/ActionStore.cs new file mode 100644 index 0000000..795a4dc --- /dev/null +++ b/Store/ActionStore.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using HermesSocketLibrary.db; +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Store.Internal; + +namespace HermesSocketServer.Store +{ + public class ActionStore : AutoSavedStore + { + private readonly string _userId; + private readonly Database _database; + private readonly Serilog.ILogger _logger; + + + public ActionStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) + { + _userId = userId; + _database = database; + _logger = logger; + } + + public override async Task Load() + { + var data = new Dictionary() { { "user", _userId } }; + string sql = $"SELECT name, type, data FROM \"Action\" WHERE \"userId\" = @user"; + await _database.Execute(sql, data, (reader) => + { + var name = reader.GetString(0); + _store.Add(name.ToString(), new RedeemableAction() + { + UserId = _userId, + Name = name, + Type = reader.GetString(1), + Data = JsonSerializer.Deserialize>(reader.GetString(2))! + }); + }); + _logger.Information($"Loaded {_store.Count} TTS chatter voices from database."); + } + + protected override void OnInitialAdd(string key, RedeemableAction value) + { + } + + protected override void OnInitialModify(string key, RedeemableAction value) + { + } + + protected override void OnInitialRemove(string key) + { + } + } +} \ No newline at end of file diff --git a/Store/ChatterStore.cs b/Store/ChatterStore.cs index 7a5b4bc..1c89482 100644 --- a/Store/ChatterStore.cs +++ b/Store/ChatterStore.cs @@ -1,30 +1,22 @@ -using System.Collections.Immutable; using HermesSocketLibrary.db; using HermesSocketServer.Models; +using HermesSocketServer.Store.Internal; namespace HermesSocketServer.Store { - public class ChatterStore : GroupSaveStore + public class ChatterStore : AutoSavedStore { private readonly string _userId; private readonly Database _database; private readonly Serilog.ILogger _logger; - private readonly GroupSaveSqlGenerator _generator; - public ChatterStore(string userId, Database database, Serilog.ILogger logger) : base(logger) + public ChatterStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) { _userId = userId; _database = database; _logger = logger; - - var ctp = new Dictionary - { - { "chatterId", "ChatterId" }, - { "ttsVoiceId", "VoiceId" }, - { "userId", "UserId" }, - }; - _generator = new GroupSaveSqlGenerator(ctp, _logger); } public override async Task Load() @@ -55,54 +47,5 @@ namespace HermesSocketServer.Store protected override void OnInitialRemove(string key) { } - - public override async Task Save() - { - int count = 0; - string sql = string.Empty; - ImmutableList? list = null; - - if (_added.Any()) - { - lock (_lock) - { - list = _added.ToImmutableList(); - _added.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedInsertSql("TtsChatVoice", count, ["userId", "chatterId", "ttsVoiceId"]); - - _logger.Debug($"Chatter - Adding {count} rows to database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["userId", "chatterId", "ttsVoiceId"]); - } - if (_modified.Any()) - { - lock (_lock) - { - list = _modified.ToImmutableList(); - _modified.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedUpdateSql("TtsChatVoice", count, ["userId", "chatterId"], ["ttsVoiceId"]); - - _logger.Debug($"Chatter - Modifying {count} rows in database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["userId", "chatterId", "ttsVoiceId"]); - } - if (_deleted.Any()) - { - lock (_lock) - { - list = _deleted.ToImmutableList(); - _deleted.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedDeleteSql("TtsChatVoice", count, ["userId", "chatterId"]); - - _logger.Debug($"Chatter - Deleting {count} rows from database: {sql}"); - await _generator.DoPreparedStatementRaw(_database, sql, list, ["userId", "chatterId"]); - } - } } } \ No newline at end of file diff --git a/Store/IStore.cs b/Store/IStore.cs index 92e3622..09814c0 100644 --- a/Store/IStore.cs +++ b/Store/IStore.cs @@ -6,7 +6,7 @@ namespace HermesSocketServer.Store IDictionary Get(); Task Load(); bool Modify(K? key, Action action); - void Remove(K? key); + bool Remove(K? key); Task Save(); bool Set(K? key, V? value); } diff --git a/Store/Internal/DatabaseTable.cs b/Store/Internal/DatabaseTable.cs new file mode 100644 index 0000000..ab72c35 --- /dev/null +++ b/Store/Internal/DatabaseTable.cs @@ -0,0 +1,11 @@ +namespace HermesSocketServer.Store.Internal +{ + public class DatabaseTable + { + public required string TableName { get; set; } + public required string[] KeyColumns { get; set; } + public required string[] DataColumns { get; set; } + public required IDictionary PropertyMapping { get; set; } + public IDictionary? TypeMapping { get; set; } + } +} \ No newline at end of file diff --git a/Store/GroupSaveSqlGenerator.cs b/Store/Internal/GroupSaveSqlGenerator.cs similarity index 81% rename from Store/GroupSaveSqlGenerator.cs rename to Store/Internal/GroupSaveSqlGenerator.cs index d9df6fe..71010c3 100644 --- a/Store/GroupSaveSqlGenerator.cs +++ b/Store/Internal/GroupSaveSqlGenerator.cs @@ -1,20 +1,29 @@ using System.Reflection; using System.Text; +using System.Text.Json; using HermesSocketLibrary.db; +using NpgsqlTypes; -namespace HermesSocketServer.Store +namespace HermesSocketServer.Store.Internal { public class GroupSaveSqlGenerator { - private readonly IDictionary columnPropertyRelations; + private readonly IDictionary _columnPropertyRelations; + private readonly IDictionary _columnTypes; private readonly Serilog.ILogger _logger; public GroupSaveSqlGenerator(IDictionary columnsToProperties, Serilog.ILogger logger) + : this(columnsToProperties, new Dictionary(), logger) { - columnPropertyRelations = columnsToProperties.ToDictionary(p => p.Key, p => typeof(T).GetProperty(p.Value)); + } + + public GroupSaveSqlGenerator(IDictionary columnsToProperties, IDictionary columnTypes, Serilog.ILogger logger) + { + _columnPropertyRelations = columnsToProperties.ToDictionary(p => p.Key, p => typeof(T).GetProperty(p.Value)); + _columnTypes = columnTypes; _logger = logger; - var nullProperties = columnPropertyRelations.Where(p => p.Value == null) + var nullProperties = _columnPropertyRelations.Where(p => p.Value == null) .Select(p => columnsToProperties[p.Key]); if (nullProperties.Any()) throw new ArgumentException("Some properties do not exist on the values given: " + string.Join(", ", nullProperties)); @@ -31,8 +40,15 @@ namespace HermesSocketServer.Store { foreach (var column in columns) { - var propValue = columnPropertyRelations[column]!.GetValue(value); - c.Parameters.AddWithValue(column.ToLower() + valueCounter, propValue ?? DBNull.Value); + var propValue = _columnPropertyRelations[column]!.GetValue(value); + if (_columnTypes.Any() && _columnTypes.TryGetValue(column, out var type)) + { + if (type == NpgsqlDbType.Jsonb) + propValue = JsonSerializer.Serialize(propValue); + c.Parameters.AddWithValue(column.ToLower() + valueCounter, type, propValue ?? DBNull.Value); + } + else + c.Parameters.AddWithValue(column.ToLower() + valueCounter, propValue ?? DBNull.Value); } valueCounter++; } @@ -81,7 +97,7 @@ namespace HermesSocketServer.Store if (!columns.Any()) throw new ArgumentException("Empty list given.", nameof(columns)); - var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); + var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES "); foreach (var value in values) @@ -89,8 +105,8 @@ namespace HermesSocketServer.Store sb.Append("("); foreach (var column in columns) { - var propValue = columnPropertyRelations[column]!.GetValue(value); - var propType = columnPropertyRelations[column]!.PropertyType; + var propValue = _columnPropertyRelations[column]!.GetValue(value); + var propType = _columnPropertyRelations[column]!.PropertyType; WriteValue(sb, propValue ?? DBNull.Value, propType); sb.Append(","); } @@ -111,7 +127,7 @@ namespace HermesSocketServer.Store if (!columns.Any()) throw new ArgumentException("Empty list given.", nameof(columns)); - var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); + var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); var columnsLower = columns.Select(c => c.ToLower()); sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES "); @@ -151,7 +167,7 @@ namespace HermesSocketServer.Store throw new ArgumentException("Empty list given.", nameof(updateColumns)); var columns = keyColumns.Union(updateColumns); - var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); + var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES "); foreach (var value in values) @@ -159,8 +175,8 @@ namespace HermesSocketServer.Store sb.Append("("); foreach (var column in columns) { - var propValue = columnPropertyRelations[column]!.GetValue(value); - var propType = columnPropertyRelations[column]!.PropertyType; + var propValue = _columnPropertyRelations[column]!.GetValue(value); + var propType = _columnPropertyRelations[column]!.PropertyType; WriteValue(sb, propValue, propType); sb.Append(","); } @@ -189,7 +205,7 @@ namespace HermesSocketServer.Store throw new ArgumentException("Empty list given.", nameof(updateColumns)); var columns = keyColumns.Union(updateColumns); - var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); + var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES "); for (var row = 0; row < rows; row++) @@ -226,7 +242,7 @@ namespace HermesSocketServer.Store if (!keyColumns.Any()) throw new ArgumentException("Empty list given.", nameof(keyColumns)); - var ctp = keyColumns.ToDictionary(c => c, c => columnPropertyRelations[c]); + var ctp = keyColumns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN ("); foreach (var k in keys) @@ -234,7 +250,7 @@ namespace HermesSocketServer.Store sb.Append("("); foreach (var column in keyColumns) { - var propType = columnPropertyRelations[column]!.PropertyType; + var propType = _columnPropertyRelations[column]!.PropertyType; WriteValue(sb, k, propType); sb.Append(","); } @@ -256,7 +272,7 @@ namespace HermesSocketServer.Store if (!keyColumns.Any()) throw new ArgumentException("Empty list given.", nameof(keyColumns)); - var ctp = keyColumns.ToDictionary(c => c, c => columnPropertyRelations[c]); + var ctp = keyColumns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN ("); for (var row = 0; row < rows; row++) @@ -289,7 +305,12 @@ namespace HermesSocketServer.Store .Append(value?.ToString()) .Append("')"); else if (type == typeof(TimeSpan)) - sb.Append(((TimeSpan)value).TotalMilliseconds); + { + if (value == null) + sb.Append("0"); + else + sb.Append(((TimeSpan)value).TotalMilliseconds); + } else sb.Append(value); } diff --git a/Store/GroupedSaveStore.cs b/Store/Internal/GroupedSaveStore.cs similarity index 95% rename from Store/GroupedSaveStore.cs rename to Store/Internal/GroupedSaveStore.cs index b97c486..cbadda8 100644 --- a/Store/GroupedSaveStore.cs +++ b/Store/Internal/GroupedSaveStore.cs @@ -2,7 +2,7 @@ using System.Collections.Immutable; -namespace HermesSocketServer.Store +namespace HermesSocketServer.Store.Internal { public abstract class GroupSaveStore : IStore where K : class where V : class { @@ -60,30 +60,30 @@ namespace HermesSocketServer.Store if (value == null) return false; + OnInitialModify(key, value); action(value); if (!_added.Contains(key) && !_modified.Contains(key)) { _modified.Add(key); _logger.Information($"added key to _modified {key}"); } - OnInitialModify(key, value); return true; } } return false; } - public void Remove(K? key) + public bool Remove(K? key) { if (key == null) - return; + return false; lock (_lock) { + OnInitialRemove(key); if (_store.Remove(key)) { _logger.Information($"removed key from _deleted {key}"); - OnInitialRemove(key); if (!_added.Remove(key)) { _modified.Remove(key); @@ -94,8 +94,10 @@ namespace HermesSocketServer.Store _logger.Information($"added key to _deleted {key}"); } } + return true; } } + return false; } public bool Set(K? key, V? value) diff --git a/Store/Internal/SelfGeneratedStore.cs b/Store/Internal/SelfGeneratedStore.cs new file mode 100644 index 0000000..fdfb3d5 --- /dev/null +++ b/Store/Internal/SelfGeneratedStore.cs @@ -0,0 +1,61 @@ + + +using System.Collections.Immutable; +using HermesSocketLibrary.db; + +namespace HermesSocketServer.Store.Internal +{ + public abstract class AutoSavedStore : GroupSaveStore where K : class where V : class + { + private readonly GroupSaveSqlGenerator _generator; + private readonly DatabaseTable _table; + private readonly Database _database; + private readonly Serilog.ILogger _logger; + + + public AutoSavedStore(DatabaseTable table, Database database, Serilog.ILogger logger) + : base(logger) + { + _generator = new GroupSaveSqlGenerator(table.PropertyMapping, logger); + _table = table; + _database = database; + _logger = logger; + } + + public override async Task Save() + { + var allColumns = _table.KeyColumns.Union(_table.DataColumns).ToArray(); + + await GenerateQuery(_added, + (size) => _generator.GeneratePreparedInsertSql(_table.TableName, size, allColumns), + async (query, _, values) => await _generator.DoPreparedStatement(_database, query, values, allColumns)); + + await GenerateQuery(_modified, + (size) => _generator.GeneratePreparedUpdateSql(_table.TableName, size, _table.KeyColumns, _table.DataColumns), + async (query, _, values) => await _generator.DoPreparedStatement(_database, query, values, allColumns)); + + await GenerateQuery(_deleted, + (size) => _generator.GeneratePreparedDeleteSql(_table.TableName, size, _table.KeyColumns), + async (query, keys, _) => await _generator.DoPreparedStatementRaw(_database, query, keys, _table.KeyColumns)); + } + + private async Task GenerateQuery(IList keys, Func generate, Func, IEnumerable, Task> execute) + { + ImmutableList? list = null; + lock (_lock) + { + if (!keys.Any()) + return; + + list = keys.ToImmutableList(); + keys.Clear(); + } + + var query = generate(list.Count); + _logger.Debug($"{_table.TableName} - Adding {list.Count} rows to database: {query}"); + + var values = list.Select(id => _store[id]).Where(v => v != null); + await execute(query, list, values); + } + } +} \ No newline at end of file diff --git a/Store/PolicyStore.cs b/Store/PolicyStore.cs index d2b8b63..4faf355 100644 --- a/Store/PolicyStore.cs +++ b/Store/PolicyStore.cs @@ -1,33 +1,22 @@ -using System.Collections.Immutable; using HermesSocketLibrary.db; using HermesSocketServer.Models; +using HermesSocketServer.Store.Internal; namespace HermesSocketServer.Store { - public class PolicyStore : GroupSaveStore + public class PolicyStore : AutoSavedStore { private readonly string _userId; private readonly Database _database; private readonly Serilog.ILogger _logger; - private readonly GroupSaveSqlGenerator _generator; - public PolicyStore(string userId, Database database, Serilog.ILogger logger) : base(logger) + public PolicyStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) { _userId = userId; _database = database; _logger = logger; - - var ctp = new Dictionary - { - { "id", "Id" }, - { "userId", "UserId" }, - { "groupId", "GroupId" }, - { "path", "Path" }, - { "count", "Usage" }, - { "timespan", "Span" }, - }; - _generator = new GroupSaveSqlGenerator(ctp, _logger); } public override async Task Load() @@ -61,54 +50,5 @@ namespace HermesSocketServer.Store protected override void OnInitialRemove(string key) { } - - public override async Task Save() - { - int count = 0; - string sql = string.Empty; - ImmutableList? list = null; - - if (_added.Any()) - { - lock (_lock) - { - list = _added.ToImmutableList(); - _added.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedInsertSql("GroupPermissionPolicy", count, ["id", "userId", "groupId", "path", "count", "timespan"]); - - _logger.Debug($"GroupPermissionPolicy - Adding {count} rows to database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "groupId", "path", "count", "timespan"]); - } - if (_modified.Any()) - { - lock (_lock) - { - list = _modified.ToImmutableList(); - _modified.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedUpdateSql("GroupPermissionPolicy", count, ["id"], ["userId", "groupId", "path", "count", "timespan"]); - - _logger.Debug($"GroupPermissionPolicy - Modifying {count} rows in database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "groupId", "path", "count", "timespan"]); - } - if (_deleted.Any()) - { - lock (_lock) - { - list = _deleted.ToImmutableList(); - _deleted.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedDeleteSql("GroupPermissionPolicy", count, ["id"]); - - _logger.Debug($"GroupPermissionPolicy - Deleting {count} rows from database: {sql}"); - await _generator.DoPreparedStatementRaw(_database, sql, list.Select(id => new Guid(id)), ["id"]); - } - } } } \ No newline at end of file diff --git a/Store/RedemptionStore.cs b/Store/RedemptionStore.cs new file mode 100644 index 0000000..6f0c716 --- /dev/null +++ b/Store/RedemptionStore.cs @@ -0,0 +1,54 @@ +using HermesSocketLibrary.db; +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Store.Internal; + +namespace HermesSocketServer.Store +{ + public class RedemptionStore : AutoSavedStore + { + private readonly string _userId; + private readonly Database _database; + private readonly Serilog.ILogger _logger; + + + public RedemptionStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) + { + _userId = userId; + _database = database; + _logger = logger; + } + + public override async Task Load() + { + var data = new Dictionary() { { "user", _userId } }; + string sql = $"SELECT id, \"redemptionId\", \"order\", \"state\", \"actionName\" FROM \"Redemption\" WHERE \"userId\" = @user"; + await _database.Execute(sql, data, (reader) => + { + var id = reader.GetGuid(0); + _store.Add(id.ToString(), new Redemption() + { + Id = id.ToString(), + UserId = _userId, + RedemptionId = reader.GetString(1), + Order = reader.GetInt32(2), + State = reader.GetBoolean(3), + ActionName = reader.GetString(4), + }); + }); + _logger.Information($"Loaded {_store.Count} TTS chatter voices from database."); + } + + protected override void OnInitialAdd(string key, Redemption value) + { + } + + protected override void OnInitialModify(string key, Redemption value) + { + } + + protected override void OnInitialRemove(string key) + { + } + } +} \ No newline at end of file diff --git a/Store/TTSFilterStore.cs b/Store/TTSFilterStore.cs index 37c3815..f8f16c8 100644 --- a/Store/TTSFilterStore.cs +++ b/Store/TTSFilterStore.cs @@ -1,31 +1,22 @@ -using System.Collections.Immutable; using HermesSocketLibrary.db; using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Store.Internal; namespace HermesSocketServer.Store { - public class TTSFilterStore : GroupSaveStore + public class TTSFilterStore : AutoSavedStore { private readonly string _userId; private readonly Database _database; private readonly Serilog.ILogger _logger; - private readonly GroupSaveSqlGenerator _generator; - public TTSFilterStore(string userId, Database database, Serilog.ILogger logger) : base(logger) + public TTSFilterStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) { _userId = userId; _database = database; _logger = logger; - - var ctp = new Dictionary - { - { "id", "Id" }, - { "userId", "UserId" }, - { "search", "Search" }, - { "replace", "Replace" }, - }; - _generator = new GroupSaveSqlGenerator(ctp, _logger); } public override async Task Load() @@ -56,54 +47,5 @@ namespace HermesSocketServer.Store protected override void OnInitialRemove(string key) { } - - public override async Task Save() - { - int count = 0; - string sql = string.Empty; - ImmutableList? list = null; - - if (_added.Any()) - { - lock (_lock) - { - list = _added.ToImmutableList(); - _added.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedInsertSql("TtsWordFilter", count, ["id", "userId", "search", "replace"]); - - _logger.Debug($"TTS Filter - Adding {count} rows to database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "search", "replace"]); - } - if (_modified.Any()) - { - lock (_lock) - { - list = _modified.ToImmutableList(); - _modified.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedUpdateSql("TtsWordFilter", count, ["id"], ["userId", "search", "replace"]); - - _logger.Debug($"TTS Filter - Modifying {count} rows in database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "search", "replace"]); - } - if (_deleted.Any()) - { - lock (_lock) - { - list = _deleted.ToImmutableList(); - _deleted.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedDeleteSql("TtsWordFilter", count, ["id"]); - - _logger.Debug($"TTS Filter - Deleting {count} rows from database: {sql}"); - await _generator.DoPreparedStatementRaw(_database, sql, list, ["id"]); - } - } } } \ No newline at end of file diff --git a/Store/UserStore.cs b/Store/UserStore.cs index 7d63128..9f7e59f 100644 --- a/Store/UserStore.cs +++ b/Store/UserStore.cs @@ -1,30 +1,20 @@ -using System.Collections.Immutable; using HermesSocketLibrary.db; using HermesSocketServer.Models; +using HermesSocketServer.Store.Internal; namespace HermesSocketServer.Store { - public class UserStore : GroupSaveStore + public class UserStore : AutoSavedStore { private readonly Database _database; private readonly Serilog.ILogger _logger; - private readonly GroupSaveSqlGenerator _generator; - public UserStore(Database database, Serilog.ILogger logger) : base(logger) + public UserStore([FromKeyedServices("User")] DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) { _database = database; _logger = logger; - - var ctp = new Dictionary - { - { "id", "Id" }, - { "name", "Name" }, - { "email", "Email" }, - { "role", "Role" }, - { "ttsDefaultVoice", "DefaultVoice" } - }; - _generator = new GroupSaveSqlGenerator(ctp, _logger); } public override async Task Load() @@ -56,54 +46,5 @@ namespace HermesSocketServer.Store protected override void OnInitialRemove(string key) { } - - public override async Task Save() - { - int count = 0; - string sql = string.Empty; - ImmutableList? list = null; - - if (_added.Any()) - { - lock (_lock) - { - list = _added.ToImmutableList(); - _added.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedInsertSql("User", count, ["id", "name", "email", "role", "ttsDefaultVoice"]); - - _logger.Debug($"User - Adding {count} rows to database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]); - } - if (_modified.Any()) - { - lock (_lock) - { - list = _modified.ToImmutableList(); - _modified.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedUpdateSql("User", count, ["id"], ["name", "email", "role", "ttsDefaultVoice"]); - - _logger.Debug($"User - Modifying {count} rows in database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]); - } - if (_deleted.Any()) - { - lock (_lock) - { - list = _deleted.ToImmutableList(); - _deleted.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedDeleteSql("User", count, ["id"]); - - _logger.Debug($"User - Deleting {count} rows from database: {sql}"); - await _generator.DoPreparedStatementRaw(_database, sql, list, ["id"]); - } - } } } \ No newline at end of file diff --git a/Store/VoiceStateStore.cs b/Store/VoiceStateStore.cs new file mode 100644 index 0000000..377b328 --- /dev/null +++ b/Store/VoiceStateStore.cs @@ -0,0 +1,55 @@ +using HermesSocketLibrary.db; +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Store.Internal; +using HermesSocketServer.Validators; + +namespace HermesSocketServer.Store +{ + public class VoiceStateStore : AutoSavedStore + { + private readonly string _userId; + private readonly VoiceIdValidator _idValidator; + private readonly Database _database; + private readonly Serilog.ILogger _logger; + + + public VoiceStateStore(string userId, VoiceIdValidator voiceIdValidator, DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) + { + _userId = userId; + _idValidator = voiceIdValidator; + _database = database; + _logger = logger; + } + + public override async Task Load() + { + var data = new Dictionary() { { "user", _userId } }; + string sql = "SELECT \"ttsVoiceId\", state FROM \"TtsVoiceState\" WHERE \"userId\" = @user"; + await _database.Execute(sql, data, (reader) => + { + string id = reader.GetString(0); + _store.Add(id, new TTSVoiceState() + { + Id = id, + Enabled = reader.GetBoolean(1), + UserId = _userId, + }); + }); + _logger.Information($"Loaded {_store.Count} TTS voice states from database."); + } + + protected override void OnInitialAdd(string key, TTSVoiceState value) + { + _idValidator.Check(value.Id); + } + + protected override void OnInitialModify(string key, TTSVoiceState value) + { + } + + protected override void OnInitialRemove(string key) + { + } + } +} \ No newline at end of file diff --git a/Store/VoiceStore.cs b/Store/VoiceStore.cs index ca3efc1..e1e673f 100644 --- a/Store/VoiceStore.cs +++ b/Store/VoiceStore.cs @@ -1,32 +1,25 @@ -using System.Collections.Immutable; using HermesSocketLibrary.db; -using HermesSocketServer.Models; +using HermesSocketLibrary.Requests.Messages; +using HermesSocketServer.Store.Internal; using HermesSocketServer.Validators; namespace HermesSocketServer.Store { - public class VoiceStore : GroupSaveStore + public class VoiceStore : AutoSavedStore { private readonly VoiceIdValidator _idValidator; private readonly VoiceNameValidator _nameValidator; private readonly Database _database; private readonly Serilog.ILogger _logger; - private readonly GroupSaveSqlGenerator _generator; - public VoiceStore(VoiceIdValidator voiceIdValidator, VoiceNameValidator voiceNameValidator, Database database, Serilog.ILogger logger) : base(logger) + public VoiceStore(VoiceIdValidator voiceIdValidator, VoiceNameValidator voiceNameValidator, [FromKeyedServices("Voice")] DatabaseTable table, Database database, Serilog.ILogger logger) + : base(table, database, logger) { _idValidator = voiceIdValidator; _nameValidator = voiceNameValidator; _database = database; _logger = logger; - - var ctp = new Dictionary - { - { "id", "Id" }, - { "name", "Name" } - }; - _generator = new GroupSaveSqlGenerator(ctp, _logger); } public override async Task Load() @@ -35,7 +28,7 @@ namespace HermesSocketServer.Store await _database.Execute(sql, new Dictionary(), (reader) => { string id = reader.GetString(0); - _store.Add(id, new Voice() + _store.Add(id, new TTSVoice() { Id = id, Name = reader.GetString(1), @@ -44,13 +37,13 @@ namespace HermesSocketServer.Store _logger.Information($"Loaded {_store.Count} TTS voices from database."); } - protected override void OnInitialAdd(string key, Voice value) + protected override void OnInitialAdd(string key, TTSVoice value) { _idValidator.Check(value.Id); _nameValidator.Check(value.Name); } - protected override void OnInitialModify(string key, Voice value) + protected override void OnInitialModify(string key, TTSVoice value) { _nameValidator.Check(value.Name); } @@ -58,54 +51,5 @@ namespace HermesSocketServer.Store protected override void OnInitialRemove(string key) { } - - public override async Task Save() - { - int count = 0; - string sql = string.Empty; - ImmutableList? list = null; - - if (_added.Any()) - { - lock (_lock) - { - list = _added.ToImmutableList(); - _added.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedInsertSql("TtsVoice", count, ["id", "name"]); - - _logger.Debug($"Voice - Adding {count} rows to database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]); - } - if (_modified.Any()) - { - lock (_lock) - { - list = _modified.ToImmutableList(); - _modified.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedUpdateSql("TtsVoice", count, ["id"], ["name"]); - - _logger.Debug($"Voice - Modifying {count} rows in database: {sql}"); - var values = list.Select(id => _store[id]).Where(v => v != null); - await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]); - } - if (_deleted.Any()) - { - lock (_lock) - { - list = _deleted.ToImmutableList(); - _deleted.Clear(); - } - count = list.Count; - sql = _generator.GeneratePreparedDeleteSql("TtsVoice", count, ["id"]); - - _logger.Debug($"Voice - Deleting {count} rows from database: {sql}"); - await _generator.DoPreparedStatementRaw(_database, sql, list, ["id"]); - } - } } } \ No newline at end of file