- Database namespace is now NadekoBot.Db

- Db related code is now in src/NadekoBot/Db
- Finished major part of the db refactor, but many optimizations are left to be made
This commit is contained in:
Kwoth
2021-06-19 06:08:09 +02:00
parent c127dcd1e3
commit d42705087e
141 changed files with 104 additions and 102 deletions

View File

@@ -0,0 +1,48 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using NadekoBot.Db.Models;
namespace NadekoBot.Db
{
public static class ClubExtensions
{
private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
=> clubs.Include(x => x.Owner)
.Include(x => x.Applicants)
.ThenInclude(x => x.User)
.Include(x => x.Bans)
.ThenInclude(x => x.User)
.Include(x => x.Users)
.AsQueryable();
public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
public static ClubInfo GetByOwnerOrAdmin(this DbSet<ClubInfo> clubs, ulong userId)
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId
|| c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
public static ClubInfo GetByMember(this DbSet<ClubInfo> clubs, ulong userId)
=> Include(clubs).FirstOrDefault(c => c.Users.Any(u => u.UserId == userId));
public static ClubInfo GetByName(this DbSet<ClubInfo> clubs, string name, int discrim)
=> Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
=> Include(clubs)
.Where(x => x.Name.ToUpper() == name.ToUpper())
.Select(x => x.Discrim)
.DefaultIfEmpty()
.Max() + 1;
public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
{
return clubs
.AsNoTracking()
.OrderByDescending(x => x.Xp)
.Skip(page * 9)
.Take(9)
.ToList();
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Db
{
public static class CurrencyTransactionExtensions
{
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
{
return set.AsQueryable()
.AsNoTracking()
.Where(x => x.UserId == userId)
.OrderByDescending(x => x.DateAdded)
.Skip(15 * page)
.Take(15)
.ToList();
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using LinqToDB;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Db
{
public static class CustomReactionsExtensions
{
public static int ClearFromGuild(this DbSet<CustomReaction> crs, ulong guildId)
{
return crs.Delete(x => x.GuildId == guildId);
}
public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
{
return crs
.AsNoTracking()
.AsQueryable()
.Where(x => x.GuildId == id)
.ToArray();
}
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
{
return crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Db
{
public static class DbExtensions
{
public static T GetById<T>(this DbSet<T> set, int id) where T: DbEntity
=> set.FirstOrDefault(x => x.Id == id);
}
}

View File

@@ -0,0 +1,170 @@
using NadekoBot.Db.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Discord;
using System.Collections.Generic;
using NadekoBot.Core.Services.Database;
namespace NadekoBot.Db
{
public static class DiscordUserExtensions
{
public static void EnsureUserCreated(this NadekoContext ctx, ulong userId, string username, string discrim, string avatarId)
{
ctx.Database.ExecuteSqlInterpolated($@"
UPDATE OR IGNORE DiscordUser
SET Username={username},
Discriminator={discrim},
AvatarId={avatarId}
WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId)
VALUES ({userId}, {username}, {discrim}, {avatarId});
");
}
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
public static DiscordUser GetOrCreateUser(this NadekoContext ctx, ulong userId, string username, string discrim, string avatarId)
{
ctx.EnsureUserCreated(userId, username, discrim, avatarId);
return ctx.DiscordUser
.Include(x => x.Club)
.First(u => u.UserId == userId);
}
public static DiscordUser GetOrCreateUser(this NadekoContext ctx, IUser original)
=> ctx.GetOrCreateUser(original.Id, original.Username, original.Discriminator, original.AvatarId);
public static int GetUserGlobalRank(this DbSet<DiscordUser> users, ulong id)
{
return users.AsQueryable()
.Where(x => x.TotalXp > (users
.AsQueryable()
.Where(y => y.UserId == id)
.Select(y => y.TotalXp)
.FirstOrDefault()))
.Count() + 1;
}
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page)
{
return users.AsQueryable()
.OrderByDescending(x => x.TotalXp)
.Skip(page * 9)
.Take(9)
.AsEnumerable()
.ToArray();
}
public static List<DiscordUser> GetTopRichest(this DbSet<DiscordUser> users, ulong botId, int count, int page = 0)
{
return users.AsQueryable()
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Skip(page * 9)
.Take(count)
.ToList();
}
public static List<DiscordUser> GetTopRichest(this DbSet<DiscordUser> users, ulong botId, int count)
{
return users.AsQueryable()
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Take(count)
.ToList();
}
public static long GetUserCurrency(this DbSet<DiscordUser> users, ulong userId) =>
users.AsNoTracking()
.FirstOrDefault(x => x.UserId == userId)
?.CurrencyAmount ?? 0;
public static void RemoveFromMany(this DbSet<DiscordUser> users, IEnumerable<ulong> ids)
{
var items = users.AsQueryable().Where(x => ids.Contains(x.UserId));
foreach (var item in items)
{
item.CurrencyAmount = 0;
}
}
public static bool TryUpdateCurrencyState(this NadekoContext ctx, ulong userId, string name, string discrim, string avatarId, long amount, bool allowNegative = false)
{
if (amount == 0)
return true;
// if remove - try to remove if he has more or equal than the amount
// and return number of rows > 0 (was there a change)
if (amount < 0 && !allowNegative)
{
var rows = ctx.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId} AND CurrencyAmount>={-amount};");
return rows > 0;
}
// if remove and negative is allowed, just remove without any condition
if (amount < 0 && allowNegative)
{
var rows = ctx.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId};");
return rows > 0;
}
// if add - create a new user with default values if it doesn't exist
// if it exists, sum current amount with the new one, if it doesn't
// he just has the new amount
var updatedUserData = !string.IsNullOrWhiteSpace(name);
name = name ?? "Unknown";
discrim = discrim ?? "????";
avatarId = avatarId ?? "";
// just update the amount, there is no new user data
if (!updatedUserData)
{
ctx.Database.ExecuteSqlInterpolated($@"
UPDATE OR IGNORE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount)
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
");
}
else
{
ctx.Database.ExecuteSqlInterpolated($@"
UPDATE OR IGNORE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount},
Username={name},
Discriminator={discrim},
AvatarId={avatarId}
WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount)
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount});
");
}
return true;
}
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
{
return users
.Sum(x => x.CurrencyAmount);
}
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
{
return users.AsQueryable()
.Where(x => x.UserId != botId)
.OrderByDescending(x => x.CurrencyAmount)
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
.Sum(x => x.CurrencyAmount);
}
}
}

View File

@@ -0,0 +1,240 @@
using NadekoBot.Core.Services.Database.Models;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System;
using NadekoBot.Core.Services.Database;
using NadekoBot.Db.Models;
namespace NadekoBot.Db
{
public static class GuildConfigExtensions
{
public class GeneratingChannel
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
}
/// <summary>
/// Gets full stream role settings for the guild with the specified id.
/// </summary>
/// <param name="ctx">Db Context</param>
/// <param name="guildId">Id of the guild to get stream role settings for.</param>
/// <returns>Guild'p stream role settings</returns>
public static StreamRoleSettings GetStreamRoleSettings(this NadekoContext ctx, ulong guildId)
{
var conf = ctx.GuildConfigsForId(guildId, set => set.Include(y => y.StreamRole)
.Include(y => y.StreamRole.Whitelist)
.Include(y => y.StreamRole.Blacklist));
if (conf.StreamRole == null)
conf.StreamRole = new StreamRoleSettings();
return conf.StreamRole;
}
private static List<WarningPunishment> DefaultWarnPunishments =>
new List<WarningPunishment>() {
new WarningPunishment() {
Count = 3,
Punishment = PunishmentAction.Kick
},
new WarningPunishment() {
Count = 5,
Punishment = PunishmentAction.Ban
}
};
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
{
return configs
.AsQueryable()
.Include(gc => gc.CommandCooldowns)
.Include(gc => gc.FollowedStreams)
.Include(gc => gc.StreamRole)
.Include(gc => gc.NsfwBlacklistedTags)
.Include(gc => gc.XpSettings)
.ThenInclude(x => x.ExclusionList)
.Include(gc => gc.DelMsgOnCmdChannels)
.Include(gc => gc.ReactionRoleMessages)
.ThenInclude(x => x.ReactionRoles)
;
}
public static IEnumerable<GuildConfig> GetAllGuildConfigs(this DbSet<GuildConfig> configs, List<ulong> availableGuilds)
=> configs
.IncludeEverything()
.AsNoTracking()
.Where(x => availableGuilds.Contains(x.GuildId))
.ToList();
/// <summary>
/// Gets and creates if it doesn't exist a config for a guild.
/// </summary>
/// <param name="guildId">For which guild</param>
/// <param name="includes">Use to manipulate the set however you want</param>
/// <returns>Config for the guild</returns>
public static GuildConfig GuildConfigsForId(this NadekoContext ctx, ulong guildId, Func<DbSet<GuildConfig>, IQueryable<GuildConfig>> includes = null)
{
GuildConfig config;
if (includes == null)
{
config = ctx
.GuildConfigs
.IncludeEverything()
.FirstOrDefault(c => c.GuildId == guildId);
}
else
{
var set = includes(ctx.GuildConfigs);
config = set.FirstOrDefault(c => c.GuildId == guildId);
}
if (config == null)
{
ctx.GuildConfigs.Add((config = new GuildConfig
{
GuildId = guildId,
Permissions = Permissionv2.GetDefaultPermlist,
WarningsInitialized = true,
WarnPunishments = DefaultWarnPunishments,
}));
ctx.SaveChanges();
}
if (!config.WarningsInitialized)
{
config.WarningsInitialized = true;
config.WarnPunishments = DefaultWarnPunishments;
}
return config;
}
public static GuildConfig LogSettingsFor(this NadekoContext ctx, ulong guildId)
{
var config = ctx
.GuildConfigs
.AsQueryable()
.Include(gc => gc.LogSetting)
.ThenInclude(gc => gc.IgnoredChannels)
.FirstOrDefault(x => x.GuildId == guildId);
if (config == null)
{
ctx.GuildConfigs.Add((config = new GuildConfig
{
GuildId = guildId,
Permissions = Permissionv2.GetDefaultPermlist,
WarningsInitialized = true,
WarnPunishments = DefaultWarnPunishments,
}));
ctx.SaveChanges();
}
if (!config.WarningsInitialized)
{
config.WarningsInitialized = true;
config.WarnPunishments = DefaultWarnPunishments;
}
return config;
}
public static IEnumerable<GuildConfig> Permissionsv2ForAll(this DbSet<GuildConfig> configs, List<ulong> include)
{
var query = configs.AsQueryable()
.Where(x => include.Contains(x.GuildId))
.Include(gc => gc.Permissions);
return query.ToList();
}
public static GuildConfig GcWithPermissionsv2For(this NadekoContext ctx, ulong guildId)
{
var config = ctx
.GuildConfigs
.AsQueryable()
.Where(gc => gc.GuildId == guildId)
.Include(gc => gc.Permissions)
.FirstOrDefault();
if (config == null) // if there is no guildconfig, create new one
{
ctx.GuildConfigs.Add((config = new GuildConfig
{
GuildId = guildId,
Permissions = Permissionv2.GetDefaultPermlist
}));
ctx.SaveChanges();
}
else if (config.Permissions == null || !config.Permissions.Any()) // if no perms, add default ones
{
config.Permissions = Permissionv2.GetDefaultPermlist;
ctx.SaveChanges();
}
return config;
}
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs)
{
return configs
.AsQueryable()
.Include(x => x.FollowedStreams)
.SelectMany(gc => gc.FollowedStreams)
.ToArray();
}
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs, List<ulong> included)
{
return configs.AsQueryable()
.Where(gc => included.Contains(gc.GuildId))
.Include(gc => gc.FollowedStreams)
.SelectMany(gc => gc.FollowedStreams)
.ToList();
}
public static void SetCleverbotEnabled(this DbSet<GuildConfig> configs, ulong id, bool cleverbotEnabled)
{
var conf = configs.FirstOrDefault(gc => gc.GuildId == id);
if (conf == null)
return;
conf.CleverbotEnabled = cleverbotEnabled;
}
public static XpSettings XpSettingsFor(this NadekoContext ctx, ulong guildId)
{
var gc = ctx.GuildConfigsForId(guildId,
set => set.Include(x => x.XpSettings)
.ThenInclude(x => x.RoleRewards)
.Include(x => x.XpSettings)
.ThenInclude(x => x.CurrencyRewards)
.Include(x => x.XpSettings)
.ThenInclude(x => x.ExclusionList));
if (gc.XpSettings == null)
gc.XpSettings = new XpSettings();
return gc.XpSettings;
}
public static IEnumerable<GeneratingChannel> GetGeneratingChannels(this DbSet<GuildConfig> configs)
{
return configs
.AsQueryable()
.Include(x => x.GenerateCurrencyChannelIds)
.Where(x => x.GenerateCurrencyChannelIds.Any())
.SelectMany(x => x.GenerateCurrencyChannelIds)
.Select(x => new GeneratingChannel()
{
ChannelId = x.ChannelId,
GuildId = x.GuildConfig.GuildId
})
.ToArray();
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Db
{
public static class MusicPlayerSettingsExtensions
{
public static async Task<MusicPlayerSettings> ForGuildAsync(this DbSet<MusicPlayerSettings> settings, ulong guildId)
{
var toReturn = await settings
.AsQueryable()
.FirstOrDefaultAsync(x => x.GuildId == guildId);
if (toReturn is null)
{
var newSettings = new MusicPlayerSettings()
{
GuildId = guildId,
PlayerRepeat = PlayerRepeatType.Queue
};
await settings.AddAsync(newSettings);
return newSettings;
}
return toReturn;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Db
{
public static class MusicPlaylistExtensions
{
public static List<MusicPlaylist> GetPlaylistsOnPage(this DbSet<MusicPlaylist> playlists, int num)
{
if (num < 1)
throw new IndexOutOfRangeException();
return playlists
.AsQueryable()
.Skip((num - 1) * 20)
.Take(20)
.Include(pl => pl.Songs)
.ToList();
}
public static MusicPlaylist GetWithSongs(this DbSet<MusicPlaylist> playlists, int id) =>
playlists
.Include(mpl => mpl.Songs)
.FirstOrDefault(mpl => mpl.Id == id);
}
}

View File

@@ -0,0 +1,46 @@
using NadekoBot.Db.Models;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Db;
namespace NadekoBot.Db
{
public static class PollExtensions
{
public static IEnumerable<Poll> GetAllPolls(this DbSet<Poll> polls)
{
return polls.Include(x => x.Answers)
.Include(x => x.Votes)
.ToArray();
}
public static void RemovePoll(this NadekoContext ctx, int id)
{
var p = ctx
.Poll
.Include(x => x.Answers)
.Include(x => x.Votes)
.FirstOrDefault(x => x.Id == id);
if (p is null)
return;
if (p.Votes != null)
{
ctx.RemoveRange(p.Votes);
p.Votes.Clear();
}
if (p.Answers != null)
{
ctx.RemoveRange(p.Answers);
p.Answers.Clear();
}
ctx.Poll.Remove(p);
}
}
}

View File

@@ -0,0 +1,53 @@
using NadekoBot.Core.Services.Database.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
namespace NadekoBot.Db
{
public static class QuoteExtensions
{
public static IEnumerable<Quote> GetGroup(this DbSet<Quote> quotes, ulong guildId, int page, OrderType order)
{
var q = quotes.AsQueryable().Where(x => x.GuildId == guildId);
if (order == OrderType.Keyword)
q = q.OrderBy(x => x.Keyword);
else
q = q.OrderBy(x => x.Id);
return q.Skip(15 * page).Take(15).ToArray();
}
public static async Task<Quote> GetRandomQuoteByKeywordAsync(this DbSet<Quote> quotes, ulong guildId, string keyword)
{
var rng = new NadekoRandom();
return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId && q.Keyword == keyword)
.ToListAsync())
.OrderBy(q => rng.Next())
.FirstOrDefault();
}
public static async Task<Quote> SearchQuoteKeywordTextAsync(this DbSet<Quote> quotes, ulong guildId, string keyword, string text)
{
var rngk = new NadekoRandom();
return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId
&& q.Keyword == keyword
&& EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
// && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase)
)
.ToListAsync())
.OrderBy(q => rngk.Next())
.FirstOrDefault();
}
public static void RemoveAllByKeyword(this DbSet<Quote> quotes, ulong guildId, string keyword)
{
quotes.RemoveRange(quotes.AsQueryable().Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword));
}
}
}

View File

@@ -0,0 +1,22 @@
using NadekoBot.Core.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Db
{
public static class ReminderExtensions
{
public static IEnumerable<Reminder> GetIncludedReminders(this DbSet<Reminder> reminders, IEnumerable<ulong> guildIds)
=> reminders.AsQueryable()
.Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0)
.ToList();
public static IEnumerable<Reminder> RemindersFor(this DbSet<Reminder> reminders, ulong userId, int page)
=> reminders.AsQueryable()
.Where(x => x.UserId == userId)
.OrderBy(x => x.DateAdded)
.Skip(page * 10)
.Take(10);
}
}

View File

@@ -0,0 +1,26 @@
using NadekoBot.Core.Services.Database.Models;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace NadekoBot.Db
{
public static class SelfAssignableRolesExtensions
{
public static bool DeleteByGuildAndRoleId(this DbSet<SelfAssignedRole> roles, ulong guildId, ulong roleId)
{
var role = roles.FirstOrDefault(s => s.GuildId == guildId && s.RoleId == roleId);
if (role == null)
return false;
roles.Remove(role);
return true;
}
public static IEnumerable<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
=> roles.AsQueryable()
.Where(s => s.GuildId == guildId)
.ToArray();
}
}

View File

@@ -0,0 +1,84 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using LinqToDB;
using NadekoBot.Core.Services.Database;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Db
{
public static class UserXpExtensions
{
public static UserXpStats GetOrCreateUserXpStats(this NadekoContext ctx, ulong guildId, ulong userId)
{
var usr = ctx.UserXpStats.FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId);
if (usr == null)
{
ctx.Add(usr = new UserXpStats()
{
Xp = 0,
UserId = userId,
NotifyOnLevelUp = XpNotificationLocation.None,
GuildId = guildId,
});
}
return usr;
}
public static List<UserXpStats> GetUsersFor(this DbSet<UserXpStats> xps, ulong guildId, int page)
{
return xps
.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Skip(page * 9)
.Take(9)
.ToList();
}
public static List<UserXpStats> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
{
return xps
.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Take(count)
.ToList();
}
public static int GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
{
// @"SELECT COUNT(*) + 1
//FROM UserXpStats
//WHERE GuildId = @p1 AND ((Xp + AwardedXp) > (SELECT Xp + AwardedXp
// FROM UserXpStats
// WHERE UserId = @p2 AND GuildId = @p1
// LIMIT 1));";
return xps
.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId && ((x.Xp + x.AwardedXp) >
(xps.AsQueryable()
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Select(y => y.Xp + y.AwardedXp)
.FirstOrDefault())
))
.Count() + 1;
}
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
{
xps.Delete(x => x.UserId == userId && x.GuildId == guildId);
}
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
{
xps.Delete(x => x.GuildId == guildId);
}
}
}

View File

@@ -0,0 +1,177 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Migrations;
using NadekoBot.Db.Models;
namespace NadekoBot.Db
{
public class WaifuInfoStats
{
public string FullName { get; set; }
public int Price { get; set; }
public string ClaimerName { get; set; }
public string AffinityName { get; set; }
public int AffinityCount { get; set; }
public int DivorceCount { get; set; }
public int ClaimCount { get; set; }
public List<WaifuItem> Items { get; set; }
public List<string> Claims { get; set; }
public List<string> Fans { get; set; }
}
public static class WaifuExtensions
{
public static WaifuInfo ByWaifuUserId(this DbSet<WaifuInfo> waifus, ulong userId, Func<DbSet<WaifuInfo>, IQueryable<WaifuInfo>> includes = null)
{
if (includes == null)
{
return waifus.Include(wi => wi.Waifu)
.Include(wi => wi.Affinity)
.Include(wi => wi.Claimer)
.Include(wi => wi.Items)
.FirstOrDefault(wi => wi.Waifu.UserId == userId);
}
return includes(waifus)
.AsQueryable()
.FirstOrDefault(wi => wi.Waifu.UserId == userId);
}
public static IEnumerable<WaifuLbResult> GetTop(this DbSet<WaifuInfo> waifus, int count, int skip = 0)
{
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
if (count == 0)
return new List<WaifuLbResult>();
return waifus.Include(wi => wi.Waifu)
.Include(wi => wi.Affinity)
.Include(wi => wi.Claimer)
.OrderByDescending(wi => wi.Price)
.Skip(skip)
.Take(count)
.Select(x => new WaifuLbResult
{
Affinity = x.Affinity == null ? null : x.Affinity.Username,
AffinityDiscrim = x.Affinity == null ? null : x.Affinity.Discriminator,
Claimer = x.Claimer == null ? null : x.Claimer.Username,
ClaimerDiscrim = x.Claimer == null ? null : x.Claimer.Discriminator,
Username = x.Waifu.Username,
Discrim = x.Waifu.Discriminator,
Price = x.Price,
})
.ToList();
}
public static decimal GetTotalValue(this DbSet<WaifuInfo> waifus)
{
return waifus
.AsQueryable()
.Where(x => x.ClaimerId != null)
.Sum(x => x.Price);
}
public static int AffinityCount(this DbSet<WaifuUpdate> updates, ulong userId)
{
return updates
.FromSqlInterpolated($@"SELECT 1
FROM WaifuUpdates
WHERE UserId = (SELECT Id from DiscordUser WHERE UserId={userId}) AND
UpdateType = 0 AND
NewId IS NOT NULL")
.Count();
}
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
{
return waifus
.AsQueryable()
.AsNoTracking()
.Where(x => x.Claimer.UserId == ownerId
&& x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
.Select(x => x.Waifu.UserId)
.FirstOrDefault();
}
public static WaifuInfoStats GetWaifuInfo(this NadekoContext ctx, ulong userId)
{
ctx.Database.ExecuteSqlInterpolated($@"
INSERT OR IGNORE INTO WaifuInfo (AffinityId, ClaimerId, Price, WaifuId)
VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}));");
var toReturn = ctx.WaifuInfo
.AsQueryable()
.Where(w => w.WaifuId == ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.UserId == userId)
.Select(u => u.Id).FirstOrDefault())
.Select(w => new WaifuInfoStats
{
FullName = ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.UserId == userId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
AffinityCount = ctx.Set<WaifuUpdate>()
.AsQueryable()
.Count(x => x.UserId == w.WaifuId &&
x.UpdateType == WaifuUpdateType.AffinityChanged &&
x.NewId != null),
AffinityName = ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.Id == w.AffinityId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
ClaimCount = ctx.WaifuInfo
.AsQueryable()
.Count(x => x.ClaimerId == w.WaifuId),
ClaimerName = ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.Id == w.ClaimerId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
DivorceCount = ctx
.Set<WaifuUpdate>()
.AsQueryable()
.Count(x => x.OldId == w.WaifuId &&
x.NewId == null &&
x.UpdateType == WaifuUpdateType.Claimed),
Price = w.Price,
Claims = ctx.WaifuInfo
.AsQueryable()
.Include(x => x.Waifu)
.Where(x => x.ClaimerId == w.WaifuId)
.Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator)
.ToList(),
Fans = ctx.WaifuInfo
.AsQueryable()
.Include(x => x.Waifu)
.Where(x => x.AffinityId == w.WaifuId)
.Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator)
.ToList(),
Items = w.Items,
})
.FirstOrDefault();
if (toReturn is null)
return null;
return toReturn;
}
}
}

View File

@@ -0,0 +1,56 @@
using NadekoBot.Core.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace NadekoBot.Db
{
public static class WarningExtensions
{
public static Warning[] ForId(this DbSet<Warning> warnings, ulong guildId, ulong userId)
{
var query = warnings.AsQueryable()
.Where(x => x.GuildId == guildId && x.UserId == userId)
.OrderByDescending(x => x.DateAdded);
return query.ToArray();
}
public static bool Forgive(this DbSet<Warning> warnings, ulong guildId, ulong userId, string mod, int index)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
var warn = warnings.AsQueryable().Where(x => x.GuildId == guildId && x.UserId == userId)
.OrderByDescending(x => x.DateAdded)
.Skip(index)
.FirstOrDefault();
if (warn == null || warn.Forgiven)
return false;
warn.Forgiven = true;
warn.ForgivenBy = mod;
return true;
}
public static async Task ForgiveAll(this DbSet<Warning> warnings, ulong guildId, ulong userId, string mod)
{
await warnings.AsQueryable().Where(x => x.GuildId == guildId && x.UserId == userId)
.ForEachAsync(x =>
{
if (x.Forgiven != true)
{
x.Forgiven = true;
x.ForgivenBy = mod;
}
});
}
public static Warning[] GetForGuild(this DbSet<Warning> warnings, ulong id)
{
return warnings.AsQueryable().Where(x => x.GuildId == id).ToArray();
}
}
}