Initial KV API concept

This commit is contained in:
Kruzya
2023-01-05 08:01:18 +03:00
parent a97fac85e4
commit 984e582ce4
8 changed files with 207 additions and 0 deletions
@@ -2,6 +2,7 @@ using System.Threading.Tasks;
using Kruzya.TelegramBot.Core.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Logging;
namespace Kruzya.TelegramBot.Core.Extensions
{
@@ -51,6 +52,9 @@ namespace Kruzya.TelegramBot.Core.Extensions
public static async Task<BotUserValue> FindOrCreateOption(this DbSet<BotUserValue> repository, long chatId,
long userId, string option)
{
repository.GetService<ILogger<Core>>().LogCritical("FindOrCreateOption() is deprecated. " +
"Please, use the new API OptionManagerService.");
var opt = await repository.FindOption(chatId, userId, option);
if (opt == null)
{
+51
View File
@@ -0,0 +1,51 @@
#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Kruzya.TelegramBot.Core.Data;
using Kruzya.TelegramBot.Core.Extensions;
using Microsoft.EntityFrameworkCore;
namespace Kruzya.TelegramBot.Core.Option;
public class CoreContextStorageService : IOptionStorageAdapter
{
private CoreContext _db;
public CoreContextStorageService(CoreContext db)
{
_db = db;
}
public async Task<byte[]?> LoadValueAsync(IOptionKey key)
{
var entity = await BuildQueryForKey(key).FirstOrDefaultAsync();
return entity?.Value;
}
public async Task SaveValueAsync(IOptionKey key, byte[] value)
{
var entity = await BuildQueryForKey(key).FirstOrDefaultAsync();
if (entity == null)
{
entity = await CreateEntity(key);
}
entity.Value = value;
_db.AddOrUpdate(entity);
}
private IQueryable<BotUserValue> BuildQueryForKey(IOptionKey key)
{
var legacyUserId = key.UserId ?? 0;
return _db.UserValues.Where(e => e.BotUser.ChatId == key.ChatId && e.BotUser.UserId == legacyUserId
&& e.Name == key.Identifier);
}
private async Task<BotUserValue> CreateEntity(IOptionKey key)
{
var entity = _db.UserValues.Create()!;
entity.BotUser = await _db.Users.FindOrCreate(key.ChatId, key.UserId ?? 0);
return entity;
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Option;
/// <summary>
/// Allows interact with option related with some chat.
/// </summary>
public interface IOptionItem<T>
{
public IOptionKey Key { get; }
public T Value { get; set; }
public bool IsChanged { get; }
public Task LoadAsync(IOptionStorageAdapter adapter);
public Task SaveAsync(IOptionStorageAdapter adapter);
}
+22
View File
@@ -0,0 +1,22 @@
namespace Kruzya.TelegramBot.Core.Option;
/// <summary>
/// Explains what
/// </summary>
public interface IOptionKey
{
/// <summary>
/// The chat identifier where this key is related.
/// </summary>
public long ChatId { get; }
/// <summary>
/// The Telegram user identifier. May be NULL if this is a simple option for all chat members.
/// </summary>
public long? UserId { get; }
/// <summary>
/// The unique option identifier.
/// </summary>
public string Identifier { get; }
}
+9
View File
@@ -0,0 +1,9 @@
namespace Kruzya.TelegramBot.Core.Option;
/// <summary>
/// Describes what API should be implemented in Option Manager Service.
/// </summary>
public interface IOptionManagerService
{
}
+26
View File
@@ -0,0 +1,26 @@
#nullable enable
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Option;
/// <summary>
/// Implements an interaction with storage for options.
/// </summary>
public interface IOptionStorageAdapter
{
/// <summary>
/// Loads a saved value (if exists) from storage.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public Task<byte[]?> LoadValueAsync(IOptionKey key);
/// <summary>
/// Saves a passed value to storage.
/// </summary>
/// <param name="key">The unique chat key</param>
/// <param name="value">The serialized value for saving</param>
/// <returns></returns>
public Task SaveValueAsync(IOptionKey key, byte[] value);
}
+53
View File
@@ -0,0 +1,53 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Kruzya.TelegramBot.Core.Option;
public class OptionItem<T> : IOptionItem<T>
{
public IOptionKey Key { get; }
public T Value
{
get => _value;
set
{
_value = value;
_isChanged = true;
}
}
public bool IsChanged => _isChanged;
private T _value;
private bool _isChanged;
public OptionItem(IOptionKey key, T value)
{
Key = key;
Value = value;
}
public async Task LoadAsync(IOptionStorageAdapter adapter)
{
var savedValue = await adapter.LoadValueAsync(Key);
if (savedValue == null)
{
return;
}
// This property changed when we're loaded default value. Reset here.
_isChanged = false;
_value = JsonSerializer.Deserialize<T>(new ReadOnlySpan<byte>(savedValue));
}
public async Task SaveAsync(IOptionStorageAdapter adapter)
{
var data = JsonSerializer.SerializeToUtf8Bytes(Value, new JsonSerializerOptions { WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
await adapter.SaveValueAsync(Key, data);
}
}
+26
View File
@@ -0,0 +1,26 @@
using Telegram.Bot.Types;
namespace Kruzya.TelegramBot.Core.Option;
public class OptionKey : IOptionKey
{
public long ChatId { get; }
public long? UserId { get; } = null;
public string Identifier { get; }
public OptionKey(Chat chat, User user, string identifier) : this(chat.Id, user.Id, identifier)
{}
public OptionKey(Chat chat, string identifier) : this(chat.Id, null, identifier)
{}
public OptionKey(long chat, string identifier) : this(chat, null, identifier)
{}
public OptionKey(long chat, long? user, string identifier)
{
ChatId = chat;
UserId = user;
Identifier = identifier;
}
}