Reworked currency service. Some features are missing

This commit is contained in:
Kwoth
2022-01-20 13:22:38 +01:00
parent fa41c5a319
commit 0ef2da6f10
31 changed files with 433 additions and 325 deletions

View File

@@ -0,0 +1,123 @@
#nullable disable
using LinqToDB;
using NadekoBot.Services.Currency;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService
{
private readonly DbService _db;
public CurrencyService(DbService db)
=> _db = db;
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
{
return Task.FromResult<IWallet>(new DefaultWallet(userId, _db.GetDbContext()));
}
throw new ArgumentOutOfRangeException(nameof(type));
}
public async Task AddBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
{
await using var ctx = _db.GetDbContext();
foreach (var userId in userIds)
{
var wallet = new DefaultWallet(userId, ctx);
await wallet.Add(amount, extra);
}
await ctx.SaveChangesAsync();
return;
}
throw new ArgumentOutOfRangeException(nameof(type));
}
public async Task RemoveBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
{
await using var ctx = _db.GetDbContext();
await ctx.DiscordUser
.Where(x => userIds.Contains(x.UserId))
.UpdateAsync(du => new()
{
CurrencyAmount = du.CurrencyAmount >= amount
? du.CurrencyAmount - amount
: 0
});
return;
}
throw new ArgumentOutOfRangeException(nameof(type));
}
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount)
=> new() { Amount = amount, UserId = userId, Reason = reason ?? "-" };
public async Task AddAsync(
ulong userId,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, extra);
}
public async Task AddAsync(
IUser user,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(user.Id);
await wallet.Add(amount, extra);
}
public async Task<bool> RemoveAsync(
ulong userId,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(userId);
return await wallet.Take(amount, extra);
}
public async Task<bool> RemoveAsync(
IUser user,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(user.Id);
return await wallet.Take(amount, extra);
}
public async Task<bool> TransferAsync(
ulong from,
ulong to,
long amount,
string note)
{
await using var fromWallet = await GetWalletAsync(@from);
await using var toWallet = await GetWalletAsync(to);
var extra = new Extra("transfer", "gift", note);
return await fromWallet.Transfer(amount, toWallet, extra);
}
}

View File

@@ -0,0 +1,6 @@
namespace NadekoBot.Services.Currency;
public enum CurrencyType
{
Default,
}

View File

@@ -0,0 +1,104 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Currency;
public class DefaultWallet : IWallet
{
public ulong UserId { get; }
private readonly NadekoContext _ctx;
public DefaultWallet(ulong userId, NadekoContext ctx)
{
UserId = userId;
_ctx = ctx;
}
public Task<long> GetBalance()
=> _ctx.DiscordUser
.ToLinqToDBTable()
.Where(x => x.UserId == UserId)
.Select(x => x.CurrencyAmount)
.FirstOrDefaultAsync();
public async Task<bool> Take(long amount, Extra extra)
{
if (amount < 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
var changed = await _ctx.DiscordUser
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
.UpdateAsync(x => new()
{
CurrencyAmount = x.CurrencyAmount - amount
});
if (changed == 0)
return false;
// todo type, subtype
// todo from? by?
await _ctx.CreateLinqToDbContext()
.InsertAsync(new CurrencyTransaction()
{
Amount = -amount,
Reason = extra.Note,
UserId = UserId,
});
return true;
}
public async Task Add(long amount, Extra extra)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
await using (var tran = await _ctx.Database.BeginTransactionAsync())
{
var changed = await _ctx.DiscordUser
.UpdateAsync(x => new()
{
CurrencyAmount = x.CurrencyAmount + amount
});
if (changed == 0)
{
await _ctx.DiscordUser
.ToLinqToDBTable()
.Value(x => x.UserId, UserId)
.Value(x => x.Username, "Unknown")
.Value(x => x.Discriminator, "????")
.Value(x => x.CurrencyAmount, amount)
.InsertAsync();
}
await tran.CommitAsync();
}
var ct = new CurrencyTransaction()
{
Amount = amount,
Reason = extra.Note,
UserId = UserId,
};
await _ctx.CreateLinqToDbContext()
.InsertAsync(ct);
}
public void Dispose()
{
_ctx.SaveChanges();
_ctx.Dispose();
}
public async ValueTask DisposeAsync()
{
await _ctx.SaveChangesAsync();
await _ctx.DisposeAsync();
}
}

View File

@@ -0,0 +1,3 @@
namespace NadekoBot.Services.Currency;
public record class Extra(string Type, string Subtype, string Note = "", ulong OtherId = 0);

View File

@@ -0,0 +1,47 @@
using NadekoBot.Services.Currency;
#nullable disable
namespace NadekoBot.Services;
public interface ICurrencyService
{
Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default);
Task AddBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default);
Task RemoveBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default);
Task AddAsync(
ulong userId,
long amount,
Extra extra);
Task AddAsync(
IUser user,
long amount,
Extra extra);
Task<bool> RemoveAsync(
ulong userId,
long amount,
Extra extra);
Task<bool> RemoveAsync(
IUser user,
long amount,
Extra extra);
Task<bool> TransferAsync(
ulong from,
ulong to,
long amount,
string note);
}

View File

@@ -0,0 +1,36 @@
namespace NadekoBot.Services.Currency;
public interface IWallet : IDisposable, IAsyncDisposable
{
public ulong UserId { get; }
public Task<long> GetBalance();
public Task<bool> Take(long amount, Extra extra);
public Task Add(long amount, Extra extra);
public async Task<bool> Transfer(
long amount,
IWallet to,
Extra extra)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
var succ = await Take(amount,
extra with
{
OtherId = to.UserId
});
if (!succ)
return false;
await to.Add(amount,
extra with
{
OtherId = UserId
});
return true;
}
}

View File

@@ -1,43 +0,0 @@
#nullable disable
namespace NadekoBot.Services;
public interface ICurrencyService
{
Task AddAsync(
ulong userId,
string reason,
long amount,
bool gamble = false);
Task AddAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false);
Task AddBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false);
Task<bool> RemoveAsync(
ulong userId,
string reason,
long amount,
bool gamble = false);
Task<bool> RemoveAsync(
IUser userId,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false);
Task RemoveBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false);
}

View File

@@ -1,3 +1,5 @@
using NadekoBot.Services.Currency;
#nullable disable
namespace NadekoBot.Services;

View File

@@ -1,185 +0,0 @@
#nullable disable
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService
{
private readonly DbService _db;
private readonly GamblingConfigService _gss;
private readonly IEmbedBuilderService _eb;
private readonly IUser _bot;
public CurrencyService(
DbService db,
DiscordSocketClient c,
GamblingConfigService gss,
IEmbedBuilderService eb)
{
_db = db;
_gss = gss;
_eb = eb;
_bot = c.CurrentUser;
}
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount)
=> new() { Amount = amount, UserId = userId, Reason = reason ?? "-" };
private bool InternalChange(
ulong userId,
string userName,
string discrim,
string avatar,
string reason,
long amount,
bool gamble,
NadekoContext uow)
{
var result = uow.TryUpdateCurrencyState(userId, userName, discrim, avatar, amount);
if (result)
{
var t = GetCurrencyTransaction(userId, reason, amount);
uow.CurrencyTransactions.Add(t);
if (gamble)
{
var t2 = GetCurrencyTransaction(_bot.Id, reason, -amount);
uow.CurrencyTransactions.Add(t2);
uow.TryUpdateCurrencyState(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId, -amount, true);
}
}
return result;
}
private async Task InternalAddAsync(
ulong userId,
string userName,
string discrim,
string avatar,
string reason,
long amount,
bool gamble)
{
if (amount < 0)
throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.",
nameof(amount));
await using var uow = _db.GetDbContext();
InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
await uow.SaveChangesAsync();
}
public Task AddAsync(
ulong userId,
string reason,
long amount,
bool gamble = false)
=> InternalAddAsync(userId, null, null, null, reason, amount, gamble);
public async Task AddAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false)
{
await InternalAddAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
if (sendMessage)
try
{
var sign = _gss.Data.Currency.Sign;
await user.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("Received Currency")
.AddField("Amount", amount + sign)
.AddField("Reason", reason));
}
catch
{
// ignored
}
}
public async Task AddBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
var amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
await using var uow = _db.GetDbContext();
for (var i = 0; i < idArray.Length; i++)
// i have to prevent same user changing more than once as it will cause db error
if (userIdHashSet.Add(idArray[i]))
InternalChange(idArray[i], null, null, null, reasonArray[i], amountArray[i], gamble, uow);
await uow.SaveChangesAsync();
}
public async Task RemoveBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
var amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
await using var uow = _db.GetDbContext();
for (var i = 0; i < idArray.Length; i++)
// i have to prevent same user changing more than once as it will cause db error
if (userIdHashSet.Add(idArray[i]))
InternalChange(idArray[i], null, null, null, reasonArray[i], -amountArray[i], gamble, uow);
await uow.SaveChangesAsync();
}
private async Task<bool> InternalRemoveAsync(
ulong userId,
string userName,
string userDiscrim,
string avatar,
string reason,
long amount,
bool gamble = false)
{
if (amount < 0)
throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.",
nameof(amount));
bool result;
await using var uow = _db.GetDbContext();
result = InternalChange(userId, userName, userDiscrim, avatar, reason, -amount, gamble, uow);
await uow.SaveChangesAsync();
return result;
}
public Task<bool> RemoveAsync(
ulong userId,
string reason,
long amount,
bool gamble = false)
=> InternalRemoveAsync(userId, null, null, null, reason, amount, gamble);
public Task<bool> RemoveAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false)
=> InternalRemoveAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
}