Compare commits

..

3 Commits

32 changed files with 379 additions and 175 deletions

View File

@ -7,5 +7,6 @@ namespace HermesSocketServer.Models
public string Id { get; set; } public string Id { get; set; }
public User User { get; set; } public User User { get; set; }
public ChatterStore Chatters { get; set; } public ChatterStore Chatters { get; set; }
public PolicyStore Policies { get; set; }
} }
} }

12
Models/Policy.cs Normal file
View File

@ -0,0 +1,12 @@
namespace HermesSocketServer.Store
{
public class Policy
{
public string Id { get; set; }
public string UserId { get; set; }
public string GroupId { get; set; }
public string Path { get; set; }
public int Usage { get; set; }
public TimeSpan Span { get; set; }
}
}

View File

@ -1,9 +1,6 @@
using System.Text.Json;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Services; using HermesSocketServer.Services;
using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests namespace HermesSocketServer.Requests
@ -11,6 +8,8 @@ namespace HermesSocketServer.Requests
public class CreateTTSUser : IRequest public class CreateTTSUser : IRequest
{ {
public string Name => "create_tts_user"; public string Name => "create_tts_user";
public string[] RequiredKeys => ["chatter", "voice"];
private ChannelManager _channels; private ChannelManager _channels;
private Database _database; private Database _database;
private readonly ServerConfiguration _configuration; private readonly ServerConfiguration _configuration;
@ -26,40 +25,31 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null)
{
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
if (long.TryParse(data["chatter"].ToString(), out long chatterId)) if (long.TryParse(data["chatter"].ToString(), out long chatterId))
data["chatter"] = chatterId; data["chatter"] = chatterId;
else else
return new RequestResult(false, "Invalid Twitch user id"); return RequestResult.Failed("Invalid Twitch user id");
if (data["voice"] is JsonElement v) data["voice"] = data["voice"].ToString();
data["voice"] = v.ToString();
else
return new RequestResult(false, "Invalid voice id");
data["user"] = sender;
var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false; var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false;
if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId) if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId)
return new RequestResult(false, "Voice is disabled on this channel."); return RequestResult.Failed("Voice is disabled on this channel.");
var channel = _channels.Get(sender); var channel = _channels.Get(sender);
if (channel == null) bool result = channel.Chatters.Set(chatterId.ToString(), new ChatterVoice()
return new RequestResult(false, null);
channel.Chatters.Set(chatterId.ToString(), new ChatterVoice()
{ {
UserId = sender, UserId = sender,
ChatterId = chatterId.ToString(), ChatterId = chatterId.ToString(),
VoiceId = data["voice"].ToString()! VoiceId = data["voice"].ToString()!
}); });
_logger.Information($"Selected a tts voice [voice: {data["voice"]}] for user [chatter: {data["chatter"]}] in channel [channel: {data["user"]}]");
return new RequestResult(true, null); if (result)
{
_logger.Information($"Selected a tts voice [voice: {data["voice"]}] for user [chatter: {data["chatter"]}] in channel [channel: {data["user"]}]");
return RequestResult.Successful(null);
}
return RequestResult.Failed("Something went wrong when updating the cache.");
} }
} }
} }

View File

@ -1,5 +1,3 @@
using System.Text.Json;
using HermesSocketLibrary.Requests;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,6 +7,7 @@ namespace HermesSocketServer.Requests
public class CreateTTSVoice : IRequest public class CreateTTSVoice : IRequest
{ {
public string Name => "create_tts_voice"; public string Name => "create_tts_voice";
public string[] RequiredKeys => ["voice"];
private IStore<string, Voice> _voices; private IStore<string, Voice> _voices;
private ILogger _logger; private ILogger _logger;
private Random _random; private Random _random;
@ -23,27 +22,20 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null) data["voice"] = data["voice"].ToString()!;
{
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
if (data["voice"] is JsonElement v)
data["voice"] = v.ToString();
else
return new RequestResult(false, "Invalid voice name.");
string id = RandomString(25); string id = RandomString(25);
_voices.Set(id, new Voice() var result = _voices.Set(id, new Voice()
{ {
Id = id, Id = id,
Name = data["voice"].ToString()! Name = data["voice"].ToString()
}); });
_logger.Information($"Added a new voice [voice: {data["voice"]}][voice id: {id}]");
return new RequestResult(true, id); if (result) {
_logger.Information($"Added a new voice [voice: {data["voice"]}][voice id: {id}]");
return RequestResult.Successful(id);
}
return RequestResult.Failed("Something went wrong when updating the cache.");
} }
private string RandomString(int length) private string RandomString(int length)

View File

@ -1,5 +1,3 @@
using System.Text.Json;
using HermesSocketLibrary.Requests;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,6 +7,7 @@ namespace HermesSocketServer.Requests
public class DeleteTTSVoice : IRequest public class DeleteTTSVoice : IRequest
{ {
public string Name => "delete_tts_voice"; public string Name => "delete_tts_voice";
public string[] RequiredKeys => ["voice"];
private IStore<string, Voice> _voices; private IStore<string, Voice> _voices;
private ILogger _logger; private ILogger _logger;
@ -20,18 +19,9 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null) _voices.Remove(data!["voice"].ToString());
{
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
if (data["voice"] is JsonElement v)
data["voice"] = v.ToString();
_voices.Remove(data["voice"].ToString());
_logger.Information($"Deleted a voice by id [voice id: {data["voice"]}]"); _logger.Information($"Deleted a voice by id [voice id: {data["voice"]}]");
return new RequestResult(true, null); return RequestResult.Successful(null);
} }
} }
} }

View File

@ -1,5 +1,4 @@
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests namespace HermesSocketServer.Requests
@ -7,6 +6,7 @@ namespace HermesSocketServer.Requests
public class GetChatterIds : IRequest public class GetChatterIds : IRequest
{ {
public string Name => "get_chatter_ids"; public string Name => "get_chatter_ids";
public string[] RequiredKeys => [];
private Database _database; private Database _database;
private ILogger _logger; private ILogger _logger;
@ -22,7 +22,7 @@ namespace HermesSocketServer.Requests
string sql = $"SELECT id FROM \"Chatter\""; string sql = $"SELECT id FROM \"Chatter\"";
await _database.Execute(sql, (IDictionary<string, object>?) null, (r) => ids.Add(r.GetInt64(0))); await _database.Execute(sql, (IDictionary<string, object>?) null, (r) => ids.Add(r.GetInt64(0)));
_logger.Information($"Fetched all chatters for channel [channel: {sender}]"); _logger.Information($"Fetched all chatters for channel [channel: {sender}]");
return new RequestResult(true, ids, notifyClientsOnAccount: false); return RequestResult.Successful(ids, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,5 +1,4 @@
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Socket.Data; using HermesSocketLibrary.Socket.Data;
namespace HermesSocketServer.Requests namespace HermesSocketServer.Requests
@ -7,7 +6,7 @@ namespace HermesSocketServer.Requests
public class GetConnections : IRequest public class GetConnections : IRequest
{ {
public string Name => "get_connections"; public string Name => "get_connections";
public string[] RequiredKeys => [];
private Database _database; private Database _database;
private Serilog.ILogger _logger; private Serilog.ILogger _logger;
@ -22,8 +21,8 @@ namespace HermesSocketServer.Requests
var temp = new Dictionary<string, object>() { { "user", sender } }; var temp = new Dictionary<string, object>() { { "user", sender } };
var connections = new List<Connection>(); var connections = new List<Connection>();
string sql3 = "select \"name\", \"type\", \"clientId\", \"accessToken\", \"grantType\", \"scope\", \"expiresAt\", \"default\" from \"Connection\" where \"userId\" = @user"; string sql = "select \"name\", \"type\", \"clientId\", \"accessToken\", \"grantType\", \"scope\", \"expiresAt\", \"default\" from \"Connection\" where \"userId\" = @user";
await _database.Execute(sql3, temp, sql => await _database.Execute(sql, temp, sql =>
connections.Add(new Connection() connections.Add(new Connection()
{ {
Name = sql.GetString(0), Name = sql.GetString(0),
@ -36,7 +35,7 @@ namespace HermesSocketServer.Requests
Default = sql.GetBoolean(7) Default = sql.GetBoolean(7)
}) })
); );
return new RequestResult(true, connections, false); return RequestResult.Successful(connections, false);
} }
} }
} }

View File

@ -1,4 +1,3 @@
using HermesSocketLibrary.Requests;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -7,6 +6,7 @@ namespace HermesSocketServer.Requests
public class GetDefaultTTSVoice : IRequest public class GetDefaultTTSVoice : IRequest
{ {
public string Name => "get_default_tts_voice"; public string Name => "get_default_tts_voice";
public string[] RequiredKeys => [];
private readonly UserStore _users; private readonly UserStore _users;
private readonly ServerConfiguration _configuration; private readonly ServerConfiguration _configuration;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -22,9 +22,9 @@ namespace HermesSocketServer.Requests
{ {
var user = _users.Get(sender); var user = _users.Get(sender);
if (user == null) if (user == null)
return new RequestResult(false, "Unable to find user data.", notifyClientsOnAccount: false); return RequestResult.Failed("Unable to find user data.", notifyClientsOnAccount: false);
return new RequestResult(true, user.DefaultVoice ?? _configuration.Tts.DefaultTtsVoice, notifyClientsOnAccount: false); return RequestResult.Successful(user.DefaultVoice ?? _configuration.Tts.DefaultTtsVoice, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,6 +1,4 @@
using System.Text.Json;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,6 +7,7 @@ namespace HermesSocketServer.Requests
public class GetEmotes : IRequest public class GetEmotes : IRequest
{ {
public string Name => "get_emotes"; public string Name => "get_emotes";
public string[] RequiredKeys => [];
private Database _database; private Database _database;
private ILogger _logger; private ILogger _logger;
@ -28,7 +27,7 @@ namespace HermesSocketServer.Requests
Name = r.GetString(1) Name = r.GetString(1)
})); }));
_logger.Information($"Fetched all emotes for channel [channel: {sender}]"); _logger.Information($"Fetched all emotes for channel [channel: {sender}]");
return new RequestResult(true, emotes, notifyClientsOnAccount: false); return RequestResult.Successful(emotes, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,5 +1,4 @@
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests namespace HermesSocketServer.Requests
@ -7,7 +6,7 @@ namespace HermesSocketServer.Requests
public class GetEnabledTTSVoices : IRequest public class GetEnabledTTSVoices : IRequest
{ {
public string Name => "get_enabled_tts_voices"; public string Name => "get_enabled_tts_voices";
public string[] RequiredKeys => [];
private readonly Database _database; private readonly Database _database;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -27,7 +26,7 @@ namespace HermesSocketServer.Requests
+ "WHERE \"userId\" = @user AND state = true"; + "WHERE \"userId\" = @user AND state = true";
await _database.Execute(sql, temp, (r) => voices.Add(r.GetString(0))); await _database.Execute(sql, temp, (r) => voices.Add(r.GetString(0)));
_logger.Information($"Fetched all enabled TTS voice for channel [channel: {sender}]"); _logger.Information($"Fetched all enabled TTS voice for channel [channel: {sender}]");
return new RequestResult(true, voices, notifyClientsOnAccount: false); return RequestResult.Successful(voices, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,5 +1,4 @@
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -8,7 +7,7 @@ namespace HermesSocketServer.Requests
public class GetPermissions : IRequest public class GetPermissions : IRequest
{ {
public string Name => "get_permissions"; public string Name => "get_permissions";
public string[] RequiredKeys => [];
private readonly Database _database; private readonly Database _database;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -56,7 +55,7 @@ namespace HermesSocketServer.Requests
GroupChatters = groupChatters, GroupChatters = groupChatters,
GroupPermissions = groupPermissions GroupPermissions = groupPermissions
}; };
return new RequestResult(true, info, notifyClientsOnAccount: false); return RequestResult.Successful(info, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System.Text.Json; using System.Text.Json;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,7 +8,7 @@ namespace HermesSocketServer.Requests
public class GetRedeemableActions : IRequest public class GetRedeemableActions : IRequest
{ {
public string Name => "get_redeemable_actions"; public string Name => "get_redeemable_actions";
public string[] RequiredKeys => [];
private readonly JsonSerializerOptions _options; private readonly JsonSerializerOptions _options;
private readonly Database _database; private readonly Database _database;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -34,7 +33,7 @@ namespace HermesSocketServer.Requests
Data = JsonSerializer.Deserialize<IDictionary<string, string>>(r.GetString(2), _options)! Data = JsonSerializer.Deserialize<IDictionary<string, string>>(r.GetString(2), _options)!
})); }));
_logger.Information($"Fetched all chatters' selected tts voice for channel [channel: {sender}]"); _logger.Information($"Fetched all chatters' selected tts voice for channel [channel: {sender}]");
return new RequestResult(true, redemptions, notifyClientsOnAccount: false); return RequestResult.Successful(redemptions, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,5 +1,4 @@
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -8,7 +7,7 @@ namespace HermesSocketServer.Requests
public class GetRedemptions : IRequest public class GetRedemptions : IRequest
{ {
public string Name => "get_redemptions"; public string Name => "get_redemptions";
public string[] RequiredKeys => [];
private readonly Database _database; private readonly Database _database;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -33,7 +32,7 @@ namespace HermesSocketServer.Requests
State = r.GetBoolean(4) State = r.GetBoolean(4)
})); }));
_logger.Information($"Fetched all redemptions for channel [channel: {sender}]"); _logger.Information($"Fetched all redemptions for channel [channel: {sender}]");
return new RequestResult(true, redemptions, notifyClientsOnAccount: false); return RequestResult.Successful(redemptions, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,4 +1,3 @@
using HermesSocketLibrary.Requests;
using HermesSocketServer.Services; using HermesSocketServer.Services;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -7,6 +6,7 @@ namespace HermesSocketServer.Requests
public class GetTTSUsers : IRequest public class GetTTSUsers : IRequest
{ {
public string Name => "get_tts_users"; public string Name => "get_tts_users";
public string[] RequiredKeys => [];
private ChannelManager _channels; private ChannelManager _channels;
private ILogger _logger; private ILogger _logger;
@ -19,12 +19,9 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
var channel = _channels.Get(sender); var channel = _channels.Get(sender);
if (channel == null) var results = channel.Chatters.Get().ToDictionary(p => p.Key, p => p.Value.VoiceId);
return new RequestResult(false, null, notifyClientsOnAccount: false);
var temp = channel.Chatters.Get().ToDictionary(p => p.Key, p => p.Value.VoiceId);
_logger.Information($"Fetched all chatters' selected tts voice for channel [channel: {sender}]"); _logger.Information($"Fetched all chatters' selected tts voice for channel [channel: {sender}]");
return new RequestResult(true, temp, notifyClientsOnAccount: false); return RequestResult.Successful(results, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,5 +1,3 @@
using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,6 +7,7 @@ namespace HermesSocketServer.Requests
public class GetTTSVoices : IRequest public class GetTTSVoices : IRequest
{ {
public string Name => "get_tts_voices"; public string Name => "get_tts_voices";
public string[] RequiredKeys => [];
private VoiceStore _voices; private VoiceStore _voices;
private ILogger _logger; private ILogger _logger;
@ -27,7 +26,7 @@ namespace HermesSocketServer.Requests
}); });
_logger.Information($"Fetched all TTS voices for channel [channel: {sender}]"); _logger.Information($"Fetched all TTS voices for channel [channel: {sender}]");
return new RequestResult(true, voices, notifyClientsOnAccount: false); return RequestResult.Successful(voices, notifyClientsOnAccount: false);
} }
} }
} }

View File

@ -1,6 +1,4 @@
using System.Text.RegularExpressions;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,6 +7,7 @@ namespace HermesSocketServer.Requests
public class GetTTSWordFilters : IRequest public class GetTTSWordFilters : IRequest
{ {
public string Name => "get_tts_word_filters"; public string Name => "get_tts_word_filters";
public string[] RequiredKeys => [];
private readonly Database _database; private readonly Database _database;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -31,7 +30,7 @@ namespace HermesSocketServer.Requests
Replace = r.GetString(2) Replace = r.GetString(2)
})); }));
_logger.Information($"Fetched all word filters for channel [channel: {sender}]"); _logger.Information($"Fetched all word filters for channel [channel: {sender}]");
return new RequestResult(true, filters, notifyClientsOnAccount: false); return RequestResult.Successful(filters, notifyClientsOnAccount: false);
} }
} }
} }

10
Requests/IRequest.cs Normal file
View File

@ -0,0 +1,10 @@
namespace HermesSocketServer.Requests
{
public interface IRequest
{
string Name { get; }
string[] RequiredKeys { get; }
Task<RequestResult> Grant(string sender, IDictionary<string, object>? data);
}
}

View File

@ -0,0 +1,9 @@
using HermesSocketLibrary.Socket.Data;
namespace HermesSocketServer.Requests
{
public interface IRequestManager
{
Task<RequestResult> Grant(string sender, RequestMessage? message);
}
}

View File

@ -0,0 +1,59 @@
using HermesSocketLibrary.Socket.Data;
using HermesSocketServer.Services;
using Serilog;
namespace HermesSocketServer.Requests
{
public class RequestManager : IRequestManager
{
private readonly IDictionary<string, IRequest> _requests;
private readonly ChannelManager _channels;
private readonly Serilog.ILogger _logger;
public RequestManager(IEnumerable<IRequest> requests, ChannelManager channels, Serilog.ILogger logger)
{
_requests = requests.ToDictionary(r => r.Name, r => r);
_channels = channels;
_logger = logger;
}
public async Task<RequestResult> Grant(string sender, RequestMessage? message)
{
if (message == null || message.Type == null)
return RequestResult.Failed("Request type does not exist.");
var channel = _channels.Get(sender);
if (channel == null)
return RequestResult.Failed("Channel does not exist.");
if (!_requests.TryGetValue(message.Type, out IRequest? request) || request == null)
{
_logger.Warning($"Could not find request [type: {message.Type}]");
return RequestResult.Failed("Request does not exist.");
}
if (request.RequiredKeys.Any())
{
if (message.Data == null)
return RequestResult.Failed($"Request is lacking data entries.");
foreach (var key in request.RequiredKeys)
{
if (!message.Data.ContainsKey(key))
return RequestResult.Failed($"Request is missing '{key}' in its data entries.");
}
}
try
{
return await request.Grant(sender, message.Data);
}
catch (Exception e)
{
_logger.Error(e, $"Failed to grant a request during processing [type: {message.Type}]");
return RequestResult.Failed("Failed to grant request during processing: " + e.Message);
}
}
}
}

26
Requests/RequestResult.cs Normal file
View File

@ -0,0 +1,26 @@
namespace HermesSocketServer.Requests
{
public class RequestResult
{
public bool Success;
public object? Result;
public bool NotifyClientsOnAccount;
private RequestResult(bool success, object? result, bool notifyClientsOnAccount = true)
{
Success = success;
Result = result;
NotifyClientsOnAccount = notifyClientsOnAccount;
}
public static RequestResult Successful(object? result, bool notifyClientsOnAccount = true)
{
return RequestResult.Successful(result, notifyClientsOnAccount);
}
public static RequestResult Failed(string error, bool notifyClientsOnAccount = true)
{
return RequestResult.Successful(error, notifyClientsOnAccount);
}
}
}

View File

@ -1,4 +1,3 @@
using HermesSocketLibrary.Requests;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -7,6 +6,7 @@ namespace HermesSocketServer.Requests
public class UpdateDefaultTTSVoice : IRequest public class UpdateDefaultTTSVoice : IRequest
{ {
public string Name => "update_default_tts_voice"; public string Name => "update_default_tts_voice";
public string[] RequiredKeys => ["user", "voice"];
private UserStore _users; private UserStore _users;
private ILogger _logger; private ILogger _logger;
@ -18,21 +18,15 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null)
{
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
data["user"] = data["user"].ToString(); data["user"] = data["user"].ToString();
data["voice"] = data["voice"].ToString(); data["voice"] = data["voice"].ToString();
var success = _users.Modify(data["user"].ToString(), (user) => user.DefaultVoice = data["voice"].ToString()!); var success = _users.Modify(data["user"].ToString(), (user) => user.DefaultVoice = data["voice"].ToString()!);
if (!success) if (!success)
return new RequestResult(false, "Unable to find user data.", notifyClientsOnAccount: false); return RequestResult.Failed("Unable to find user data.", notifyClientsOnAccount: false);
_logger.Information($"Updated default TTS voice for channel [channel: {sender}][voice: {data["voice"]}]"); _logger.Information($"Updated default TTS voice for channel [channel: {sender}][voice: {data["voice"]}]");
return new RequestResult(true, null); return RequestResult.Successful(null);
} }
} }
} }

View File

@ -1,10 +1,6 @@
using System.Text.Json;
using System.Threading.Channels;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Services; using HermesSocketServer.Services;
using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests namespace HermesSocketServer.Requests
@ -12,7 +8,7 @@ namespace HermesSocketServer.Requests
public class UpdateTTSUser : IRequest public class UpdateTTSUser : IRequest
{ {
public string Name => "update_tts_user"; public string Name => "update_tts_user";
public string[] RequiredKeys => ["chatter", "voice"];
private ChannelManager _channels; private ChannelManager _channels;
private Database _database; private Database _database;
private readonly ServerConfiguration _configuration; private readonly ServerConfiguration _configuration;
@ -29,34 +25,27 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null)
{
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
if (long.TryParse(data["chatter"].ToString(), out long chatterId)) if (long.TryParse(data["chatter"].ToString(), out long chatterId))
data["chatter"] = chatterId; data["chatter"] = chatterId;
if (data["voice"] is JsonElement v) data["voice"] = data["voice"].ToString();
data["voice"] = v.ToString();
data["user"] = sender;
var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false; var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false;
if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId) if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId)
return new RequestResult(false, null); return RequestResult.Failed("Voice is either non-existent or disabled on this channel.");
var channel = _channels.Get(sender); var channel = _channels.Get(sender);
if (channel == null) var result = channel.Chatters.Set(chatterId.ToString(), new ChatterVoice()
return new RequestResult(false, null);
channel.Chatters.Set(chatterId.ToString(), new ChatterVoice()
{ {
UserId = sender, UserId = sender,
ChatterId = chatterId.ToString(), ChatterId = chatterId.ToString(),
VoiceId = data["voice"].ToString()! VoiceId = data["voice"].ToString()!
}); });
_logger.Information($"Updated chatter's [chatter: {data["chatter"]}] selected tts voice [voice: {data["voice"]}] in channel [channel: {sender}]"); if (result)
return new RequestResult(true, null); {
_logger.Information($"Updated chatter's [chatter: {data["chatter"]}] selected tts voice [voice: {data["voice"]}] in channel [channel: {sender}]");
return RequestResult.Successful(null);
}
return RequestResult.Failed("Soemthing went wrong when updating the cache.");
} }
} }
} }

View File

@ -1,5 +1,3 @@
using System.Text.Json;
using HermesSocketLibrary.Requests;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -9,6 +7,7 @@ namespace HermesSocketServer.Requests
public class UpdateTTSVoice : IRequest public class UpdateTTSVoice : IRequest
{ {
public string Name => "update_tts_voice"; public string Name => "update_tts_voice";
public string[] RequiredKeys => ["voice", "voiceId"];
private IStore<string, Voice> _voices; private IStore<string, Voice> _voices;
private ILogger _logger; private ILogger _logger;
@ -20,24 +19,20 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null) data["voice"] = data["voice"].ToString();
{ data["voiceid"] = data["voiceid"].ToString();
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
if (data["voice"] is JsonElement v) var result = _voices.Set(data["voiceid"].ToString(), new Voice()
data["voice"] = v.ToString();
if (data["voiceid"] is JsonElement id)
data["voiceid"] = id.ToString();
_voices.Set(data["voiceid"].ToString(), new Voice()
{ {
Id = data["voiceid"].ToString()!, Id = data["voiceid"].ToString()!,
Name = data["voice"].ToString()! Name = data["voice"].ToString()!
}); });
_logger.Information($"Updated voice's [voice id: {data["voiceid"]}] name [new name: {data["voice"]}]"); if (result)
return new RequestResult(true, null); {
_logger.Information($"Updated voice's [voice id: {data["voiceid"]}] name [new name: {data["voice"]}]");
return RequestResult.Successful(null);
}
return RequestResult.Failed("Something went wrong when updating the cache.");
} }
} }
} }

View File

@ -1,6 +1,4 @@
using System.Text.Json;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests namespace HermesSocketServer.Requests
@ -8,6 +6,7 @@ namespace HermesSocketServer.Requests
public class UpdateTTSVoiceState : IRequest public class UpdateTTSVoiceState : IRequest
{ {
public string Name => "update_tts_voice_state"; public string Name => "update_tts_voice_state";
public string[] RequiredKeys => ["voice", "state"];
private Database _database; private Database _database;
private ILogger _logger; private ILogger _logger;
@ -19,22 +18,16 @@ namespace HermesSocketServer.Requests
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data) public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
{ {
if (data == null) data["voice"] = data["voice"].ToString();
{ data["state"] = data["state"].ToString() == "True";
_logger.Warning("Data received from request is null. Ignoring it.");
return new RequestResult(false, null);
}
if (data["voice"] is JsonElement voice)
data["voice"] = voice.ToString();
if (data["state"] is JsonElement state)
data["state"] = state.ToString() == "True";
data["user"] = sender; data["user"] = sender;
string sql = "INSERT INTO \"TtsVoiceState\" (\"userId\", \"ttsVoiceId\", state) VALUES (@user, @voice, @state) ON CONFLICT (\"userId\", \"ttsVoiceId\") DO UPDATE SET state = @state"; string sql = "INSERT INTO \"TtsVoiceState\" (\"userId\", \"ttsVoiceId\", state) VALUES (@user, @voice, @state) ON CONFLICT (\"userId\", \"ttsVoiceId\") DO UPDATE SET state = @state";
var result = await _database.Execute(sql, data); var result = await _database.Execute(sql, data);
_logger.Information($"Updated voice's [voice id: {data["voice"]}] state [new state: {data["state"]}][channel: {data["user"]}]"); _logger.Information($"Updated voice's [voice id: {data["voice"]}] state [new state: {data["state"]}][channel: {data["user"]}]");
return new RequestResult(result == 1, null); if (result > 0)
return RequestResult.Successful(null);
return RequestResult.Failed("Something went wrong when updating the database.");
} }
} }
} }

View File

@ -11,6 +11,7 @@ namespace HermesSocketServer.Services
private readonly Database _database; private readonly Database _database;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly IDictionary<string, Channel> _channels; private readonly IDictionary<string, Channel> _channels;
private readonly object _lock;
public ChannelManager(UserStore users, Database database, Serilog.ILogger logger) public ChannelManager(UserStore users, Database database, Serilog.ILogger logger)
{ {
@ -18,32 +19,45 @@ namespace HermesSocketServer.Services
_database = database; _database = database;
_logger = logger; _logger = logger;
_channels = new ConcurrentDictionary<string, Channel>(); _channels = new ConcurrentDictionary<string, Channel>();
_lock = new object();
} }
public async Task Add(string userId) public async Task<Channel?> Add(string userId)
{ {
var user = _users.Get(userId); var user = _users.Get(userId);
if (user == null) if (user == null)
{ {
return; return null;
} }
if (_channels.ContainsKey(userId)) lock (_lock)
{ {
return; if (_channels.ContainsKey(userId))
{
return null;
}
} }
var chatters = new ChatterStore(userId, _database, _logger); var chatters = new ChatterStore(userId, _database, _logger);
await chatters.Load(); var policies = new PolicyStore(userId, _database, _logger);
await Task.WhenAll([
chatters.Load(),
policies.Load(),
]);
var channel = new Channel() var channel = new Channel()
{ {
Id = userId, Id = userId,
User = user, User = user,
Chatters = chatters Chatters = chatters,
Policies = policies
}; };
_channels.Add(userId, channel); lock (_lock)
{
_channels.Add(userId, channel);
}
return channel;
} }
public Channel? Get(string channelId) public Channel? Get(string channelId)
@ -52,5 +66,28 @@ namespace HermesSocketServer.Services
return channel; return channel;
return null; return null;
} }
public async Task Save(string userId)
{
if (!_channels.TryGetValue(userId, out var channel))
return;
await Task.WhenAll([
channel.Chatters.Save(),
channel.Policies.Save(),
]);
}
public async Task Save()
{
foreach (var channel in _channels.Values)
{
_logger.Debug($"Saving channel data to database [channel id: {channel.Id}][channel name: {channel.User.Name}]");
await Task.WhenAll([
channel.Chatters.Save(),
channel.Policies.Save(),
]);
}
}
} }
} }

View File

@ -4,13 +4,15 @@ namespace HermesSocketServer.Services
{ {
public class DatabaseService : BackgroundService public class DatabaseService : BackgroundService
{ {
private readonly ChannelManager _channels;
private readonly VoiceStore _voices; private readonly VoiceStore _voices;
private readonly UserStore _users; private readonly UserStore _users;
private readonly ServerConfiguration _configuration; private readonly ServerConfiguration _configuration;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
public DatabaseService(VoiceStore voices, UserStore users, ServerConfiguration configuration, Serilog.ILogger logger) public DatabaseService(ChannelManager channels, VoiceStore voices, UserStore users, ServerConfiguration configuration, Serilog.ILogger logger)
{ {
_channels = channels;
_voices = voices; _voices = voices;
_users = users; _users = users;
_configuration = configuration; _configuration = configuration;
@ -26,14 +28,16 @@ namespace HermesSocketServer.Services
await Task.Run(async () => await Task.Run(async () =>
{ {
var tasks = new List<Task>();
await Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds)); await Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds));
while (true) while (true)
{ {
tasks.Add(_voices.Save()); await Task.WhenAll([
tasks.Add(_users.Save()); _voices.Save(),
tasks.Add(Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds))); _users.Save(),
await Task.WhenAll(tasks); _channels.Save(),
Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds)),
]);
} }
}); });
} }

View File

@ -55,10 +55,13 @@ namespace HermesSocketServer.Socket.Handlers
sender.WebLogin = data.WebLogin; sender.WebLogin = data.WebLogin;
} }
await _manager.Add(userId);
var channel = _manager.Get(userId); var channel = _manager.Get(userId);
if (channel == null) if (channel == null)
return; {
channel = await _manager.Add(userId);
if (channel == null)
return;
}
sender.Name = channel.User.Name; sender.Name = channel.User.Name;
sender.Admin = channel.User.Role == "ADMIN"; sender.Admin = channel.User.Role == "ADMIN";

View File

@ -1,5 +1,5 @@
using HermesSocketLibrary.Requests;
using HermesSocketLibrary.Socket.Data; using HermesSocketLibrary.Socket.Data;
using HermesSocketServer.Requests;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Socket.Handlers namespace HermesSocketServer.Socket.Handlers

View File

@ -69,7 +69,7 @@ namespace HermesSocketServer.Store
_added.Clear(); _added.Clear();
} }
_logger.Debug($"ADD {count} " + sql); _logger.Debug($"TtsChatVoice - Adding {count} rows to database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
if (_modified.Any()) if (_modified.Any())
@ -80,7 +80,7 @@ namespace HermesSocketServer.Store
sql = _generator.GenerateUpdateSql("TtsChatVoice", _modified.Select(m => _store[m]), ["userId", "chatterId"], ["ttsVoiceId"]); sql = _generator.GenerateUpdateSql("TtsChatVoice", _modified.Select(m => _store[m]), ["userId", "chatterId"], ["ttsVoiceId"]);
_modified.Clear(); _modified.Clear();
} }
_logger.Debug($"MOD {count} " + sql); _logger.Debug($"TtsChatVoice - Modifying {count} rows in database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
if (_deleted.Any()) if (_deleted.Any())
@ -91,7 +91,7 @@ namespace HermesSocketServer.Store
sql = _generator.GenerateDeleteSql("TtsChatVoice", _deleted, ["userId", "chatterId"]); sql = _generator.GenerateDeleteSql("TtsChatVoice", _deleted, ["userId", "chatterId"]);
_deleted.Clear(); _deleted.Clear();
} }
_logger.Debug($"DEL {count} " + sql); _logger.Debug($"TtsChatVoice - Deleting {count} rows from database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
return true; return true;

105
Store/PolicyStore.cs Normal file
View File

@ -0,0 +1,105 @@
using HermesSocketLibrary.db;
namespace HermesSocketServer.Store
{
public class PolicyStore : GroupSaveStore<string, Policy>
{
private readonly string _userId;
private readonly Database _database;
private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<Policy> _generator;
public PolicyStore(string userId, Database database, Serilog.ILogger logger) : base(logger)
{
_userId = userId;
_database = database;
_logger = logger;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "userId", "UserId" },
{ "groupId", "GroupId" },
{ "path", "Path" },
{ "count", "Usage" },
{ "timespan", "Span" },
};
_generator = new GroupSaveSqlGenerator<Policy>(ctp);
}
public override async Task Load()
{
var data = new Dictionary<string, object>() { { "user", _userId } };
string sql = $"SELECT id, \"groupId\", path, count, timespan FROM \"GroupPermissionPolicy\" WHERE \"userId\" = @user";
await _database.Execute(sql, data, (reader) =>
{
string id = reader.GetString(0).ToString();
_store.Add(id, new Policy()
{
Id = id,
UserId = _userId,
GroupId = reader.GetString(1),
Path = reader.GetString(2),
Usage = reader.GetInt32(3),
Span = TimeSpan.FromMilliseconds(reader.GetInt32(4)),
});
});
_logger.Information($"Loaded {_store.Count} policies from database.");
}
protected override void OnInitialAdd(string key, Policy value)
{
}
protected override void OnInitialModify(string key, Policy value)
{
}
protected override void OnInitialRemove(string key)
{
}
public override async Task<bool> Save()
{
int count = 0;
string sql = string.Empty;
if (_added.Any())
{
lock (_lock)
{
count = _added.Count;
sql = _generator.GenerateInsertSql("GroupPermissionPolicy", _added.Select(a => _store[a]), ["id", "userId", "groupId", "path", "count", "timespan"]);
_added.Clear();
}
_logger.Debug($"GroupPermissionPolicy - Adding {count} rows to database: {sql}");
await _database.ExecuteScalar(sql);
}
if (_modified.Any())
{
lock (_lock)
{
count = _modified.Count;
sql = _generator.GenerateUpdateSql("GroupPermissionPolicy", _modified.Select(m => _store[m]), ["id"], ["userId", "groupId", "path", "count", "timespan"]);
_modified.Clear();
}
_logger.Debug($"GroupPermissionPolicy - Modifying {count} rows in database: {sql}");
await _database.ExecuteScalar(sql);
}
if (_deleted.Any())
{
lock (_lock)
{
count = _deleted.Count;
sql = _generator.GenerateDeleteSql("GroupPermissionPolicy", _deleted, ["id"]);
_deleted.Clear();
}
_logger.Debug($"GroupPermissionPolicy - Deleting {count} rows from database: {sql}");
await _database.ExecuteScalar(sql);
}
return true;
}
}
}

View File

@ -58,34 +58,40 @@ namespace HermesSocketServer.Store
public override async Task<bool> Save() public override async Task<bool> Save()
{ {
int count = 0;
string sql = string.Empty;
if (_added.Any()) if (_added.Any())
{ {
string sql = string.Empty;
lock (_lock) lock (_lock)
{ {
count = _added.Count;
sql = _generator.GenerateInsertSql("User", _added.Select(a => _store[a]), ["id", "name", "email", "role", "ttsDefaultVoice"]); sql = _generator.GenerateInsertSql("User", _added.Select(a => _store[a]), ["id", "name", "email", "role", "ttsDefaultVoice"]);
_added.Clear(); _added.Clear();
} }
_logger.Debug($"User - Adding {count} rows to database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
if (_modified.Any()) if (_modified.Any())
{ {
string sql = string.Empty;
lock (_lock) lock (_lock)
{ {
count = _modified.Count;
sql = _generator.GenerateUpdateSql("User", _modified.Select(m => _store[m]), ["id"], ["name", "email", "role", "ttsDefaultVoice"]); sql = _generator.GenerateUpdateSql("User", _modified.Select(m => _store[m]), ["id"], ["name", "email", "role", "ttsDefaultVoice"]);
_modified.Clear(); _modified.Clear();
} }
_logger.Debug($"User - Modifying {count} rows in database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
if (_deleted.Any()) if (_deleted.Any())
{ {
string sql = string.Empty;
lock (_lock) lock (_lock)
{ {
count = _deleted.Count;
sql = _generator.GenerateDeleteSql("User", _deleted, ["id"]); sql = _generator.GenerateDeleteSql("User", _deleted, ["id"]);
_deleted.Clear(); _deleted.Clear();
} }
_logger.Debug($"User - Deleting {count} rows from database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
return true; return true;

View File

@ -72,7 +72,7 @@ namespace HermesSocketServer.Store
_added.Clear(); _added.Clear();
} }
_logger.Debug($"ADD {count} " + sql); _logger.Debug($"TtsVoice - Adding {count} rows to database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
if (_modified.Any()) if (_modified.Any())
@ -83,7 +83,7 @@ namespace HermesSocketServer.Store
sql = _generator.GenerateUpdateSql("TtsVoice", _modified.Select(m => _store[m]), ["id"], ["name"]); sql = _generator.GenerateUpdateSql("TtsVoice", _modified.Select(m => _store[m]), ["id"], ["name"]);
_modified.Clear(); _modified.Clear();
} }
_logger.Debug($"MOD {count} " + sql); _logger.Debug($"TtsVoice - Modifying {count} rows in database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
if (_deleted.Any()) if (_deleted.Any())
@ -94,7 +94,7 @@ namespace HermesSocketServer.Store
sql = _generator.GenerateDeleteSql("TtsVoice", _deleted, ["id"]); sql = _generator.GenerateDeleteSql("TtsVoice", _deleted, ["id"]);
_deleted.Clear(); _deleted.Clear();
} }
_logger.Debug($"DEL {count} " + sql); _logger.Debug($"TtsVoice - Deleting {count} rows from database: {sql}");
await _database.ExecuteScalar(sql); await _database.ExecuteScalar(sql);
} }
return true; return true;