IW4M-Admin/SharedLibraryCore/Services/ClientService.cs
RaidMax 08c883e0ff fix duplicate bot welcomes
fix prompt bool incorrect default value
rename GameEvent.Remote to GameEvent.IsRemote
include NetworkId in webfront claims
fix non descript error message appearing when something fails and localization is not initialized
2019-01-03 14:39:22 -06:00

436 lines
17 KiB
C#

using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Services
{
public class ClientService : Interfaces.IEntityService<EFClient>
{
public async Task<EFClient> Create(EFClient entity)
{
using (var context = new DatabaseContext())
{
var client = new EFClient()
{
Level = Permission.User,
FirstConnection = DateTime.UtcNow,
Connections = 1,
LastConnection = DateTime.UtcNow,
Masked = false,
NetworkId = entity.NetworkId,
AliasLink = new EFAliasLink()
{
Active = false
},
ReceivedPenalties = new List<EFPenalty>()
};
client.CurrentAlias = new Alias()
{
Name = entity.Name,
Link = client.AliasLink,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
// the first time a client is created, we may not have their ip,
// so we create a temporary alias
Active = false
};
context.Clients.Add(client);
await context.SaveChangesAsync();
return client;
}
}
private async Task UpdateAlias(string name, int? ip, EFClient entity, DatabaseContext context)
{
// entity is the tracked db context item
// get all aliases by IP address and LinkId
var iqAliases = context.Aliases
.Include(a => a.Link)
.Where(a => (a.IPAddress == ip) ||
a.LinkId == entity.AliasLinkId);
#if DEBUG == true
var aliasSql = iqAliases.ToSql();
#endif
var aliases = await iqAliases.ToListAsync();
// see if they have a matching IP + Name but new NetworkId
var existingExactAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip);
bool exactAliasMatch = existingExactAlias != null;
// if existing alias matches link them
EFAliasLink aliasLink = existingExactAlias?.Link;
// if no exact matches find the first IP that matches
aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link;
// if no matches are found, use our current one
aliasLink = aliasLink ?? entity.AliasLink;
bool hasExistingAlias = aliases.Count > 0;
// this happens when an alias exists but the current link is a temporary one
if ((exactAliasMatch || hasExistingAlias) &&
(!entity.AliasLink.Active && entity.AliasLinkId != aliasLink.AliasLinkId))
{
entity.AliasLinkId = aliasLink.AliasLinkId;
entity.AliasLink = aliasLink;
//entity.CurrentServer.Logger.WriteDebug($"Updating alias link for {entity}");
await context.SaveChangesAsync();
foreach (var alias in aliases.Union(new List<EFAlias>() { entity.CurrentAlias })
.Where(_alias => !_alias.Active ||
_alias.LinkId != aliasLink.AliasLinkId))
{
entity.CurrentServer.Logger.WriteDebug($"{entity} updating alias-link id is {alias.LinkId}");
alias.Active = true;
alias.LinkId = aliasLink.AliasLinkId;
}
//entity.CurrentServer.Logger.WriteDebug($"Saving updated aliases for {entity}");
await context.SaveChangesAsync();
// todo: fix this
/*context.AliasLinks.Remove(entity.AliasLink);
entity.AliasLink = null;
//entity.CurrentServer.Logger.WriteDebug($"Removing temporary link for {entity}");
try
{
await context.SaveChangesAsync();
}
catch
{
// entity.CurrentServer.Logger.WriteDebug($"Failed to remove link for {entity}");
}*/
}
// the existing alias matches ip and name, so we can just ignore the temporary one
if (exactAliasMatch)
{
entity.CurrentServer.Logger.WriteDebug($"{entity} has exact alias match");
entity.CurrentAliasId = existingExactAlias.AliasId;
entity.CurrentAlias = existingExactAlias;
await context.SaveChangesAsync();
}
// theres no exact match, but they've played before with the GUID or IP
else if (hasExistingAlias)
{
//entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}");
// this happens when a temporary alias gets updated
if (entity.CurrentAlias.Name == name && entity.CurrentAlias.IPAddress == null)
{
entity.CurrentAlias.IPAddress = ip;
entity.CurrentAlias.Active = true;
//entity.CurrentServer.Logger.WriteDebug($"Updating temporary alias for {entity}");
await context.SaveChangesAsync();
}
else
{
var newAlias = new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
LinkId = aliasLink.AliasLinkId,
Name = name,
Active = true,
};
entity.CurrentAlias = newAlias;
//entity.CurrentServer.Logger.WriteDebug($"Saving new alias for {entity}");
await context.SaveChangesAsync();
}
}
// no record of them playing
else
{
//entity.CurrentServer.Logger.WriteDebug($"{entity} has not be seen before");
entity.AliasLink.Active = true;
entity.CurrentAlias.Active = true;
entity.CurrentAlias.IPAddress = ip;
entity.CurrentAlias.Name = name;
//entity.CurrentServer.Logger.WriteDebug($"updating new alias for {entity}");
await context.SaveChangesAsync();
}
var linkIds = aliases.Select(a => a.LinkId);
if (linkIds.Count() > 0 &&
aliases.Count(_alias => _alias.Name == name && _alias.IPAddress == ip) > 0)
{
var highestLevel = await context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId))
.MaxAsync(c => c.Level);
if (entity.Level != highestLevel)
{
entity.CurrentServer.Logger.WriteDebug($"{entity} updating user level");
// todo: log level changes here
context.Update(entity);
entity.Level = highestLevel;
await context.SaveChangesAsync();
}
}
}
public async Task<EFClient> Delete(EFClient entity)
{
using (var context = new DatabaseContext())
{
var client = context.Clients
.Single(e => e.ClientId == entity.ClientId);
entity.Active = false;
context.Entry(entity).State = EntityState.Modified;
await context.SaveChangesAsync();
return entity;
}
}
public async Task<IList<EFClient>> Find(Func<EFClient, bool> e)
{
return await Task.Run(() =>
{
using (var context = new DatabaseContext(true))
{
return context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Where(e).ToList();
}
});
}
public async Task<EFClient> Get(int entityID)
{
using (var context = new DatabaseContext(true))
{
var iqClient = from client in context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Include(c => c.Meta)
where client.ClientId == entityID
select new
{
Client = client,
LinkedAccounts = (from linkedClient in context.Clients
where client.AliasLinkId == linkedClient.AliasLinkId
select new
{
linkedClient.ClientId,
linkedClient.NetworkId
})
};
#if DEBUG == true
var clientSql = iqClient.ToSql();
#endif
var foundClient = await iqClient.FirstOrDefaultAsync();
if (foundClient == null)
{
return null;
}
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// todo: find out the best way to do this
// I'm doing this here because I don't know the best way to have multiple awaits in the query
foreach (var linked in foundClient.LinkedAccounts)
{
foundClient.Client.LinkedAccounts.Add(linked.ClientId, linked.NetworkId);
}
return foundClient.Client;
}
}
private static readonly Func<DatabaseContext, long, Task<EFClient>> _getUniqueQuery =
EF.CompileAsyncQuery((DatabaseContext context, long networkId) =>
context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Include(c => c.ReceivedPenalties)
.FirstOrDefault(c => c.NetworkId == networkId)
);
public async Task<EFClient> GetUnique(long entityAttribute)
{
using (var context = new DatabaseContext(true))
{
return await _getUniqueQuery(context, entityAttribute);
}
}
public async Task UpdateAlias(EFClient entity)
{
using (var context = new DatabaseContext())
{
var client = context.Clients
.Include(c => c.AliasLink)
.Include(c => c.CurrentAlias)
.First(e => e.ClientId == entity.ClientId);
client.CurrentServer = entity.CurrentServer;
await UpdateAlias(entity.Name, entity.IPAddress, client, context);
entity.CurrentAlias = client.CurrentAlias;
entity.CurrentAliasId = client.CurrentAliasId;
entity.AliasLink = client.AliasLink;
entity.AliasLinkId = client.AliasLinkId;
}
}
public async Task<EFClient> Update(EFClient entity)
{
using (var context = new DatabaseContext())
{
// grab the context version of the entity
var client = context.Clients
.First(e => e.ClientId == entity.ClientId);
client.CurrentServer = entity.CurrentServer;
// if their level has been changed
if (entity.Level != client.Level)
{
// get all clients that use the same aliasId
var matchingClients = context.Clients
.Where(c => c.CurrentAliasId == client.CurrentAliasId)
// make sure we don't select ourselves twice
.Where(c => c.ClientId != entity.ClientId);
// update all related clients level
await matchingClients.ForEachAsync(c =>
{
// todo: log that it has changed here
c.Level = entity.Level;
});
}
// set remaining non-navigation properties that may have been updated
client.Level = entity.Level;
client.LastConnection = entity.LastConnection;
client.Connections = entity.Connections;
client.FirstConnection = entity.FirstConnection;
client.Masked = entity.Masked;
client.TotalConnectionTime = entity.TotalConnectionTime;
client.Password = entity.Password;
client.PasswordSalt = entity.PasswordSalt;
// update in database
await context.SaveChangesAsync();
// this is set so future updates don't trigger a new alias add
if (entity.CurrentAlias.AliasId == 0)
{
entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId;
}
return client;
}
}
#region ServiceSpecific
public async Task<IList<EFClient>> GetOwners()
{
using (var context = new DatabaseContext())
{
return await context.Clients
.Where(c => c.Level == Permission.Owner)
.ToListAsync();
}
}
public async Task<List<EFClient>> GetPrivilegedClients()
{
using (var context = new DatabaseContext(disableTracking: true))
{
var iqClients = from client in context.Clients
where client.Level >= Permission.Trusted
where client.Active
select new EFClient()
{
AliasLinkId = client.AliasLinkId,
CurrentAlias = client.CurrentAlias,
ClientId = client.ClientId,
Level = client.Level,
Password = client.Password,
PasswordSalt = client.PasswordSalt,
NetworkId = client.NetworkId
};
#if DEBUG == true
var clientsSql = iqClients.ToSql();
#endif
return await iqClients.ToListAsync();
}
}
public async Task<IList<EFClient>> FindClientsByIdentifier(string identifier)
{
if (identifier.Length < 3)
{
return new List<EFClient>();
}
identifier = identifier.ToLower();
using (var context = new DatabaseContext(disableTracking: true))
{
long networkId = identifier.ConvertLong();
int? ipAddress = identifier.ConvertToIP();
var iqLinkIds = (from alias in context.Aliases
where (alias.IPAddress != null && alias.IPAddress == ipAddress) ||
alias.Name.ToLower().Contains(identifier)
select alias.LinkId).Distinct();
var linkIds = iqLinkIds.ToList();
var iqClients = context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId) ||
networkId == c.NetworkId)
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children);
#if DEBUG == true
var iqClientsSql = iqClients.ToSql();
#endif
return await iqClients.ToListAsync();
}
}
public async Task<int> GetTotalClientsAsync()
{
using (var context = new DatabaseContext(true))
{
return await context.Clients
.CountAsync();
}
}
public Task<EFClient> CreateProxy()
{
throw new NotImplementedException();
}
#endregion
}
}