2018-11-25 21:00:36 -05:00
using Microsoft.EntityFrameworkCore ;
2018-04-08 02:44:42 -04:00
using SharedLibraryCore.Database.Models ;
2019-04-25 22:05:35 -04:00
using SharedLibraryCore.Dtos ;
2020-05-24 22:22:26 -04:00
using SharedLibraryCore.Helpers ;
using SharedLibraryCore.Interfaces ;
2018-11-25 21:00:36 -05:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2020-11-27 22:52:52 -05:00
using System.Threading ;
2018-11-25 21:00:36 -05:00
using System.Threading.Tasks ;
2021-03-22 12:09:25 -04:00
using Data.Abstractions ;
using Data.Context ;
2020-11-11 18:31:26 -05:00
using Microsoft.Extensions.Logging ;
using Serilog.Context ;
2021-03-22 12:09:25 -04:00
using static Data . Models . Client . EFClient ;
2020-11-11 18:31:26 -05:00
using ILogger = Microsoft . Extensions . Logging . ILogger ;
2021-03-22 12:09:25 -04:00
using Data.Models ;
2021-08-14 18:55:28 -04:00
using SharedLibraryCore.Configuration ;
2017-11-25 20:29:58 -05:00
2018-04-08 02:44:42 -04:00
namespace SharedLibraryCore.Services
2017-11-25 20:29:58 -05:00
{
2020-05-24 22:22:26 -04:00
public class ClientService : IEntityService < EFClient > , IResourceQueryHelper < FindClientRequest , FindClientResult >
2017-11-25 20:29:58 -05:00
{
2020-05-24 22:22:26 -04:00
private readonly IDatabaseContextFactory _contextFactory ;
2020-11-11 18:31:26 -05:00
private readonly ILogger _logger ;
2021-08-14 18:55:28 -04:00
private readonly ApplicationConfiguration _appConfig ;
2020-05-24 22:22:26 -04:00
2021-08-14 18:55:28 -04:00
public ClientService ( ILogger < ClientService > logger , IDatabaseContextFactory databaseContextFactory ,
ApplicationConfiguration appConfig )
2020-05-24 22:22:26 -04:00
{
_contextFactory = databaseContextFactory ;
2020-11-11 18:31:26 -05:00
_logger = logger ;
2021-08-14 18:55:28 -04:00
_appConfig = appConfig ;
2020-05-24 22:22:26 -04:00
}
2017-11-25 20:29:58 -05:00
public async Task < EFClient > Create ( EFClient entity )
{
2021-08-14 18:55:28 -04:00
if ( ! _appConfig . EnableImplicitAccountLinking )
{
return await HandleNewCreate ( entity ) ;
}
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( true ) ;
2020-11-11 18:31:26 -05:00
using ( LogContext . PushProperty ( "Server" , entity ? . CurrentServer ? . ToString ( ) ) )
2017-11-25 20:29:58 -05:00
{
2019-04-05 14:34:03 -04:00
int? linkId = null ;
int? aliasId = null ;
2020-04-04 13:40:23 -04:00
entity . Name = entity . Name . CapClientName ( EFAlias . MAX_NAME_LENGTH ) ;
2019-04-05 14:34:03 -04:00
if ( entity . IPAddress ! = null )
{
2019-11-15 15:50:20 -05:00
var existingAliases = await context . Aliases
2019-04-05 14:34:03 -04:00
. Select ( _alias = > new { _alias . AliasId , _alias . LinkId , _alias . IPAddress , _alias . Name } )
2019-11-15 15:50:20 -05:00
. Where ( _alias = > _alias . IPAddress = = entity . IPAddress )
. ToListAsync ( ) ;
2019-04-05 14:34:03 -04:00
2019-11-15 15:50:20 -05:00
if ( existingAliases . Count > 0 )
2019-04-05 14:34:03 -04:00
{
2020-01-21 19:08:18 -05:00
linkId = existingAliases . OrderBy ( _alias = > _alias . LinkId ) . First ( ) . LinkId ;
2019-04-06 22:48:49 -04:00
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[create] client with new GUID {entity} has existing link {linkId}" , entity . ToString ( ) , linkId ) ;
2019-11-15 15:50:20 -05:00
var existingExactAlias = existingAliases . FirstOrDefault ( _alias = > _alias . Name = = entity . Name ) ;
if ( existingExactAlias ! = null )
2019-04-05 14:34:03 -04:00
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[create] client with new GUID {entity} has existing alias {aliasId}" , entity . ToString ( ) , existingExactAlias . AliasId ) ;
2019-11-15 15:50:20 -05:00
aliasId = existingExactAlias . AliasId ;
2019-04-05 14:34:03 -04:00
}
}
}
2018-11-25 21:00:36 -05:00
var client = new EFClient ( )
{
Level = Permission . User ,
FirstConnection = DateTime . UtcNow ,
LastConnection = DateTime . UtcNow ,
2019-04-05 14:34:03 -04:00
NetworkId = entity . NetworkId
2018-11-25 21:00:36 -05:00
} ;
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[create] adding {entity} to context" , entity . ToString ( ) ) ;
2020-11-27 22:52:52 -05:00
2019-06-11 09:00:14 -04:00
2019-04-05 22:15:17 -04:00
// they're just using a new GUID
if ( aliasId . HasValue )
2018-11-25 21:00:36 -05:00
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[create] setting {entity}'s alias id and linkid to ({aliasId}, {linkId})" , entity . ToString ( ) , aliasId , linkId ) ;
2019-04-05 22:15:17 -04:00
client . CurrentAliasId = aliasId . Value ;
2019-04-05 14:34:03 -04:00
client . AliasLinkId = linkId . Value ;
}
2019-04-05 22:15:17 -04:00
// link was found but they don't have an exact alias
else if ( ! aliasId . HasValue & & linkId . HasValue )
2019-04-05 14:34:03 -04:00
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[create] setting {entity}'s linkid to {linkId}, but creating new alias" , entity . ToString ( ) , linkId ) ;
2019-04-05 22:15:17 -04:00
client . AliasLinkId = linkId . Value ;
2019-04-06 22:48:49 -04:00
client . CurrentAlias = new EFAlias ( )
2019-04-05 22:15:17 -04:00
{
Name = entity . Name ,
2019-08-02 19:04:34 -04:00
SearchableName = entity . Name . StripColors ( ) . ToLower ( ) ,
2019-04-05 22:15:17 -04:00
DateAdded = DateTime . UtcNow ,
IPAddress = entity . IPAddress ,
LinkId = linkId . Value
} ;
2019-04-05 14:34:03 -04:00
}
2019-04-05 22:15:17 -04:00
// brand new players (supposedly)
2019-04-05 14:34:03 -04:00
else
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[create] creating new Link and Alias for {entity}" , entity . ToString ( ) ) ;
2019-04-06 22:48:49 -04:00
var link = new EFAliasLink ( ) ;
var alias = new EFAlias ( )
2019-04-05 14:34:03 -04:00
{
Name = entity . Name ,
2019-08-02 19:04:34 -04:00
SearchableName = entity . Name . StripColors ( ) . ToLower ( ) ,
2019-04-05 14:34:03 -04:00
DateAdded = DateTime . UtcNow ,
IPAddress = entity . IPAddress ,
2019-04-06 22:48:49 -04:00
Link = link
2019-04-05 14:34:03 -04:00
} ;
2019-04-06 22:48:49 -04:00
client . AliasLink = link ;
client . CurrentAlias = alias ;
2019-04-05 14:34:03 -04:00
}
2018-11-25 21:00:36 -05:00
2020-11-27 22:52:52 -05:00
context . Clients . Add ( client ) ;
2018-11-25 21:00:36 -05:00
await context . SaveChangesAsync ( ) ;
return client ;
}
}
2021-08-14 18:55:28 -04:00
private async Task < EFClient > HandleNewCreate ( EFClient entity )
{
await using var context = _contextFactory . CreateContext ( true ) ;
using ( LogContext . PushProperty ( "Server" , entity . CurrentServer ? . ToString ( ) ) )
{
var existingAlias = await context . Aliases
. Select ( alias = > new { alias . AliasId , alias . LinkId , alias . IPAddress , alias . Name } )
. Where ( alias = > alias . IPAddress ! = null & & alias . IPAddress = = entity . IPAddress & &
alias . Name = = entity . Name )
. FirstOrDefaultAsync ( ) ;
var client = new EFClient
{
Level = Permission . User ,
FirstConnection = DateTime . UtcNow ,
LastConnection = DateTime . UtcNow ,
NetworkId = entity . NetworkId
} ;
if ( existingAlias = = null )
{
_logger . LogDebug ( "[{Method}] creating new Link and Alias for {Entity}" , nameof ( HandleNewCreate ) , entity . ToString ( ) ) ;
var link = new EFAliasLink ( ) ;
var alias = new EFAlias ( )
{
Name = entity . Name ,
SearchableName = entity . Name . StripColors ( ) . ToLower ( ) ,
DateAdded = DateTime . UtcNow ,
IPAddress = entity . IPAddress ,
Link = link
} ;
client . CurrentAlias = alias ;
client . AliasLink = link ;
}
else
{
_logger . LogDebug ( "[{Method}] associating new GUID {Guid} with existing alias id {aliasId} for {Entity}" ,
nameof ( HandleNewCreate ) , entity . GuidString , existingAlias . AliasId , entity . ToString ( ) ) ;
client . CurrentAliasId = existingAlias . AliasId ;
client . AliasLinkId = existingAlias . LinkId ;
}
context . Clients . Add ( client ) ;
await context . SaveChangesAsync ( ) ;
return client ;
}
}
2021-03-22 12:09:25 -04:00
private async Task UpdateAlias ( string originalName , int? ip , Data . Models . Client . EFClient entity , DatabaseContext context )
2018-11-25 21:00:36 -05:00
{
2019-01-02 19:32:39 -05:00
{
2020-11-11 18:31:26 -05:00
string name = originalName . CapClientName ( EFAlias . MAX_NAME_LENGTH ) ;
// entity is the tracked db context item
// get all aliases by IP address and LinkId
var iqAliases = context . Aliases
. Include ( a = > a . Link )
// we only want alias that have the same IP address or share a link
. Where ( _alias = > _alias . IPAddress = = ip | | ( _alias . LinkId = = entity . AliasLinkId ) ) ;
var aliases = await iqAliases . ToListAsync ( ) ;
var currentIPs = aliases . Where ( _a2 = > _a2 . IPAddress ! = null ) . Select ( _a2 = > _a2 . IPAddress ) . Distinct ( ) ;
var floatingIPAliases = await context . Aliases . Where ( _alias = > currentIPs . Contains ( _alias . IPAddress ) )
. ToListAsync ( ) ;
aliases . AddRange ( floatingIPAliases ) ;
// see if they have a matching IP + Name but new NetworkId
var existingExactAlias = aliases . OrderBy ( _alias = > _alias . LinkId )
. FirstOrDefault ( a = > a . Name = = name & & a . IPAddress = = ip ) ;
bool hasExactAliasMatch = existingExactAlias ! = null ;
// if existing alias matches link them
var newAliasLink = existingExactAlias ? . Link ;
// if no exact matches find the first IP or LinkId that matches
newAliasLink = newAliasLink ? ? aliases . OrderBy ( _alias = > _alias . LinkId ) . FirstOrDefault ( ) ? . Link ;
// if no matches are found, use our current one ( it will become permanent )
newAliasLink = newAliasLink ? ? entity . AliasLink ;
bool hasExistingAlias = aliases . Count > 0 ;
bool isAliasLinkUpdated = newAliasLink . AliasLinkId ! = entity . AliasLink . AliasLinkId ;
2018-12-31 21:52:19 -05:00
2019-04-02 21:20:37 -04:00
await context . SaveChangesAsync ( ) ;
2020-11-11 18:31:26 -05:00
int distinctLinkCount = aliases . Select ( _alias = > _alias . LinkId ) . Distinct ( ) . Count ( ) ;
// this happens when the link we found is different than the one we create before adding an IP
if ( isAliasLinkUpdated | | distinctLinkCount > 1 )
2020-02-06 19:35:30 -05:00
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug (
"[updatealias] found a link for {entity} so we are updating link from {oldAliasLinkId} to {newAliasLinkId}" ,
entity . ToString ( ) , entity . AliasLink . AliasLinkId , newAliasLink . AliasLinkId ) ;
2018-12-31 21:52:19 -05:00
2020-11-11 18:31:26 -05:00
var completeAliasLinkIds = aliases . Select ( _item = > _item . LinkId )
. Append ( entity . AliasLinkId )
. Distinct ( )
. ToList ( ) ;
2019-04-02 21:20:37 -04:00
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[updatealias] updating aliasLinks {links} for IP {ip} to {linkId}" ,
string . Join ( ',' , completeAliasLinkIds ) , ip , newAliasLink . AliasLinkId ) ;
2019-04-02 21:20:37 -04:00
2020-11-11 18:31:26 -05:00
// update all the clients that have the old alias link
2019-04-12 23:25:18 -04:00
await context . Clients
2020-11-11 18:31:26 -05:00
. Where ( _client = > completeAliasLinkIds . Contains ( _client . AliasLinkId ) )
. ForEachAsync ( _client = > _client . AliasLinkId = newAliasLink . AliasLinkId ) ;
// we also need to update all the penalties or they get deleted
// scenario
// link1 joins with ip1
// link2 joins with ip2,
// link2 receives penalty
// link2 joins with ip1
// pre existing link for link2 detected
// link2 is deleted
// link2 penalties are orphaned
await context . Penalties
. Where ( _penalty = > completeAliasLinkIds . Contains ( _penalty . LinkId ) )
. ForEachAsync ( _penalty = > _penalty . LinkId = newAliasLink . AliasLinkId ) ;
entity . AliasLink = newAliasLink ;
entity . AliasLinkId = newAliasLink . AliasLinkId ;
// update all previous aliases
await context . Aliases
. Where ( _alias = > completeAliasLinkIds . Contains ( _alias . LinkId ) )
. ForEachAsync ( _alias = > _alias . LinkId = newAliasLink . AliasLinkId ) ;
2019-04-12 23:25:18 -04:00
await context . SaveChangesAsync ( ) ;
2020-11-11 18:31:26 -05:00
// we want to delete the now inactive alias
if ( newAliasLink . AliasLinkId ! = entity . AliasLinkId )
2019-11-15 15:50:20 -05:00
{
2020-11-11 18:31:26 -05:00
context . AliasLinks . Remove ( entity . AliasLink ) ;
2019-11-15 15:50:20 -05:00
await context . SaveChangesAsync ( ) ;
}
2019-04-02 21:20:37 -04:00
}
2018-11-25 21:00:36 -05:00
2020-11-11 18:31:26 -05:00
// the existing alias matches ip and name, so we can just ignore the temporary one
if ( hasExactAliasMatch )
{
_logger . LogDebug ( "[updatealias] {entity} has exact alias match" , entity . ToString ( ) ) ;
var oldAlias = entity . CurrentAlias ;
entity . CurrentAliasId = existingExactAlias . AliasId ;
entity . CurrentAlias = existingExactAlias ;
await context . SaveChangesAsync ( ) ;
// the alias is the same so we can just remove it
if ( oldAlias . AliasId ! = existingExactAlias . AliasId & & oldAlias . AliasId > 0 )
{
await context . Clients
. Where ( _client = > _client . CurrentAliasId = = oldAlias . AliasId )
. ForEachAsync ( _client = > _client . CurrentAliasId = existingExactAlias . AliasId ) ;
await context . SaveChangesAsync ( ) ;
if ( context . Entry ( oldAlias ) . State ! = EntityState . Deleted )
{
_logger . LogDebug (
"[updatealias] {entity} has exact alias match, so we're going to try to remove aliasId {aliasId} with linkId {linkId}" ,
entity . ToString ( ) , oldAlias . AliasId , oldAlias . LinkId ) ;
context . Aliases . Remove ( oldAlias ) ;
await context . SaveChangesAsync ( ) ;
}
}
}
2018-12-31 21:52:19 -05:00
2020-11-11 18:31:26 -05:00
// theres no exact match, but they've played before with the GUID or IP
else
2019-01-02 19:32:39 -05:00
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[updatealias] {entity} is using a new alias" , entity . ToString ( ) ) ;
2018-12-31 21:52:19 -05:00
2020-11-11 18:31:26 -05:00
var newAlias = new EFAlias ( )
{
DateAdded = DateTime . UtcNow ,
IPAddress = ip ,
LinkId = newAliasLink . AliasLinkId ,
Name = name ,
SearchableName = name . StripColors ( ) . ToLower ( ) ,
Active = true ,
} ;
entity . CurrentAlias = newAlias ;
entity . CurrentAliasId = 0 ;
await context . SaveChangesAsync ( ) ;
}
2019-01-02 19:32:39 -05:00
}
2019-04-02 21:20:37 -04:00
}
2018-11-25 21:00:36 -05:00
2021-08-14 18:55:28 -04:00
private async Task UpdateAliasNew ( string originalName , int? ip , Data . Models . Client . EFClient entity ,
DatabaseContext context )
{
var name = originalName . CapClientName ( EFAlias . MAX_NAME_LENGTH ) ;
var existingAliases = await context . Aliases
2021-08-14 21:43:20 -04:00
. Where ( alias = > alias . Name = = name & & alias . LinkId = = entity . AliasLinkId | |
alias . Name = = name & & alias . IPAddress ! = null & & alias . IPAddress = = ip )
2021-08-14 18:55:28 -04:00
. ToListAsync ( ) ;
var defaultAlias = existingAliases . FirstOrDefault ( alias = > alias . IPAddress = = null ) ;
var existingExactAlias =
existingAliases . FirstOrDefault ( alias = > alias . IPAddress ! = null & & alias . IPAddress = = ip ) ;
if ( defaultAlias ! = null & & existingExactAlias = = null )
{
defaultAlias . IPAddress = ip ;
entity . CurrentAlias = defaultAlias ;
2021-08-16 14:20:54 -04:00
entity . CurrentAliasId = defaultAlias . AliasId ;
2021-08-14 18:55:28 -04:00
await context . SaveChangesAsync ( ) ;
return ;
}
if ( existingExactAlias ! = null )
{
2021-08-16 14:20:54 -04:00
entity . CurrentAlias = existingExactAlias ;
entity . CurrentAliasId = existingExactAlias . AliasId ;
2021-09-04 13:33:25 -04:00
await context . SaveChangesAsync ( ) ;
2021-08-16 14:20:54 -04:00
_logger . LogDebug ( "[{Method}] client {Client} already has an existing exact alias, so we are not making changes" , nameof ( UpdateAliasNew ) , entity . ToString ( ) ) ;
2021-08-14 18:55:28 -04:00
return ;
}
_logger . LogDebug ( "[{Method}] {Entity} is using a new alias" , nameof ( UpdateAliasNew ) , entity . ToString ( ) ) ;
var newAlias = new EFAlias ( )
{
DateAdded = DateTime . UtcNow ,
IPAddress = ip ,
LinkId = entity . AliasLinkId ,
Name = name ,
SearchableName = name . StripColors ( ) . ToLower ( ) ,
Active = true ,
} ;
entity . CurrentAlias = newAlias ;
await context . SaveChangesAsync ( ) ;
2021-08-16 14:20:54 -04:00
entity . CurrentAliasId = newAlias . AliasId ;
2021-08-14 18:55:28 -04:00
}
2019-04-02 21:20:37 -04:00
/// <summary>
/// updates the permission level of the given target to the given permission level
/// </summary>
/// <param name="newPermission"></param>
/// <param name="temporalClient"></param>
/// <param name="origin"></param>
/// <param name="ctx"></param>
/// <returns></returns>
2020-05-16 12:54:01 -04:00
public virtual async Task UpdateLevel ( Permission newPermission , EFClient temporalClient , EFClient origin )
2019-04-02 21:20:37 -04:00
{
2020-11-27 22:52:52 -05:00
await using var ctx = _contextFactory . CreateContext ( true ) ;
var entity = await ctx . Clients
. Where ( _client = > _client . ClientId = = temporalClient . ClientId )
. FirstAsync ( ) ;
2019-04-02 21:20:37 -04:00
2020-11-27 22:52:52 -05:00
var oldPermission = entity . Level ;
2019-04-05 14:34:03 -04:00
2020-11-27 22:52:52 -05:00
entity . Level = newPermission ;
await ctx . SaveChangesAsync ( ) ;
2019-04-05 14:34:03 -04:00
2021-03-22 12:09:25 -04:00
using ( LogContext . PushProperty ( "Server" , temporalClient ? . CurrentServer ? . ToString ( ) ) )
2020-11-27 22:52:52 -05:00
{
_logger . LogInformation ( "Updated {clientId} to {newPermission}" , temporalClient . ClientId , newPermission ) ;
var linkedPermissionSet = new [ ] { Permission . Banned , Permission . Flagged } ;
// if their permission level has been changed to level that needs to be updated on all accounts
if ( linkedPermissionSet . Contains ( newPermission ) | | linkedPermissionSet . Contains ( oldPermission ) )
2018-12-03 20:21:13 -05:00
{
2020-11-27 22:52:52 -05:00
//get all clients that have the same linkId
var iqMatchingClients = ctx . Clients
. Where ( _client = > _client . AliasLinkId = = entity . AliasLinkId ) ;
2019-04-02 21:20:37 -04:00
2021-08-25 12:02:37 -04:00
var iqLinkClients = new List < Data . Models . Client . EFClient > ( ) . AsQueryable ( ) ;
if ( ! _appConfig . EnableImplicitAccountLinking )
{
var linkIds = await ctx . Aliases . Where ( alias = >
alias . IPAddress ! = null & & alias . IPAddress = = temporalClient . IPAddress )
. Select ( alias = > alias . LinkId )
. ToListAsync ( ) ;
iqLinkClients = ctx . Clients . Where ( client = > linkIds . Contains ( client . AliasLinkId ) ) ;
}
2020-11-27 22:52:52 -05:00
// this updates the level for all the clients with the same LinkId
// only if their new level is flagged or banned
2021-08-25 12:02:37 -04:00
await iqMatchingClients . Union ( iqLinkClients ) . ForEachAsync ( _client = >
2019-04-02 21:20:37 -04:00
{
2020-11-27 22:52:52 -05:00
_client . Level = newPermission ;
_logger . LogInformation ( "Updated linked {clientId} to {newPermission}" , _client . ClientId ,
newPermission ) ;
} ) ;
2019-04-02 21:20:37 -04:00
2020-11-27 22:52:52 -05:00
await ctx . SaveChangesAsync ( ) ;
2019-04-05 14:34:03 -04:00
}
2017-11-25 20:29:58 -05:00
}
2019-04-02 21:20:37 -04:00
temporalClient . Level = newPermission ;
2017-11-25 20:29:58 -05:00
}
public async Task < EFClient > Delete ( EFClient entity )
{
2020-11-27 22:52:52 -05:00
throw new NotImplementedException ( ) ;
2017-11-25 20:29:58 -05:00
}
2019-06-11 09:00:14 -04:00
public Task < IList < EFClient > > Find ( Func < EFClient , bool > e )
2017-11-25 20:29:58 -05:00
{
2019-06-11 09:00:14 -04:00
throw new NotImplementedException ( ) ;
2017-11-25 20:29:58 -05:00
}
2019-11-15 15:50:20 -05:00
public async Task < EFClient > Get ( int entityId )
2017-11-25 20:29:58 -05:00
{
2019-11-15 15:50:20 -05:00
// todo: this needs to be optimized for large linked accounts
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
2019-11-18 10:25:39 -05:00
2020-11-27 22:52:52 -05:00
var client = context . Clients
. Select ( _client = > new EFClient ( )
2019-11-15 15:50:20 -05:00
{
2020-11-27 22:52:52 -05:00
ClientId = _client . ClientId ,
AliasLinkId = _client . AliasLinkId ,
Level = _client . Level ,
Connections = _client . Connections ,
FirstConnection = _client . FirstConnection ,
LastConnection = _client . LastConnection ,
Masked = _client . Masked ,
NetworkId = _client . NetworkId ,
CurrentAlias = new EFAlias ( )
2019-11-15 15:50:20 -05:00
{
2020-11-27 22:52:52 -05:00
Name = _client . CurrentAlias . Name ,
IPAddress = _client . CurrentAlias . IPAddress
} ,
TotalConnectionTime = _client . TotalConnectionTime
} )
. FirstOrDefault ( _client = > _client . ClientId = = entityId ) ;
2019-10-07 11:26:07 -04:00
2020-11-27 22:52:52 -05:00
if ( client = = null )
{
return null ;
}
2018-05-14 13:55:10 -04:00
2020-11-27 22:52:52 -05:00
client . AliasLink = new EFAliasLink ( )
{
AliasLinkId = client . AliasLinkId ,
Children = await context . Aliases
. Where ( _alias = > _alias . LinkId = = client . AliasLinkId )
. Select ( _alias = > new EFAlias ( )
2018-11-25 21:00:36 -05:00
{
2020-11-27 22:52:52 -05:00
Name = _alias . Name ,
IPAddress = _alias . IPAddress
} ) . ToListAsync ( )
} ;
2018-05-24 15:48:57 -04:00
2020-11-27 22:52:52 -05:00
var foundClient = new
{
Client = client ,
LinkedAccounts = await context . Clients . Where ( _client = > _client . AliasLinkId = = client . AliasLinkId )
. Select ( _linkedClient = > new
2018-11-25 21:00:36 -05:00
{
2020-11-27 22:52:52 -05:00
_linkedClient . ClientId ,
_linkedClient . NetworkId
} )
. ToListAsync ( )
} ;
2018-05-14 13:55:10 -04:00
2020-11-27 22:52:52 -05:00
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 ) ;
2018-02-10 23:33:42 -05:00
}
2020-11-27 22:52:52 -05:00
return foundClient . Client ;
2017-11-25 20:29:58 -05:00
}
2018-09-13 15:34:42 -04:00
private static readonly Func < DatabaseContext , long , Task < EFClient > > _getUniqueQuery =
EF . CompileAsyncQuery ( ( DatabaseContext context , long networkId ) = >
context . Clients
2019-11-15 15:50:20 -05:00
. Select ( _client = > new EFClient ( )
{
ClientId = _client . ClientId ,
AliasLinkId = _client . AliasLinkId ,
Level = _client . Level ,
Connections = _client . Connections ,
FirstConnection = _client . FirstConnection ,
LastConnection = _client . LastConnection ,
Masked = _client . Masked ,
2019-11-25 13:05:12 -05:00
NetworkId = _client . NetworkId ,
2020-04-22 21:51:04 -04:00
TotalConnectionTime = _client . TotalConnectionTime ,
2020-04-29 18:05:36 -04:00
AliasLink = _client . AliasLink ,
Password = _client . Password ,
PasswordSalt = _client . PasswordSalt
2019-11-15 15:50:20 -05:00
} )
2018-09-13 15:34:42 -04:00
. FirstOrDefault ( c = > c . NetworkId = = networkId )
) ;
2020-04-22 19:46:41 -04:00
public virtual async Task < EFClient > GetUnique ( long entityAttribute )
2017-11-25 20:29:58 -05:00
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
return await _getUniqueQuery ( context , entityAttribute ) ;
2017-11-25 20:29:58 -05:00
}
2019-04-02 21:20:37 -04:00
public async Task UpdateAlias ( EFClient temporalClient )
2017-11-25 20:29:58 -05:00
{
2021-03-22 12:09:25 -04:00
await using var context = _contextFactory . CreateContext ( enableTracking : true ) ;
2018-09-11 15:28:37 -04:00
2020-11-27 22:52:52 -05:00
var entity = context . Clients
. Include ( c = > c . AliasLink )
. Include ( c = > c . CurrentAlias )
. First ( e = > e . ClientId = = temporalClient . ClientId ) ;
2019-01-02 19:32:39 -05:00
2021-08-14 18:55:28 -04:00
if ( _appConfig . EnableImplicitAccountLinking )
{
await UpdateAlias ( temporalClient . Name , temporalClient . IPAddress , entity , context ) ;
}
else
{
await UpdateAliasNew ( temporalClient . Name , temporalClient . IPAddress , entity , context ) ;
}
2020-11-27 22:52:52 -05:00
temporalClient . CurrentAlias = entity . CurrentAlias ;
temporalClient . CurrentAliasId = entity . CurrentAliasId ;
temporalClient . AliasLink = entity . AliasLink ;
temporalClient . AliasLinkId = entity . AliasLinkId ;
2019-01-02 19:32:39 -05:00
}
2019-04-02 21:20:37 -04:00
public async Task < EFClient > Update ( EFClient temporalClient )
2019-01-02 19:32:39 -05:00
{
2019-12-27 21:37:50 -05:00
if ( temporalClient . ClientId < 1 )
{
2020-11-11 18:31:26 -05:00
_logger . LogDebug ( "[update] {client} needs to be updated but they do not have a valid client id, ignoring.." , temporalClient . ToString ( ) ) ;
2019-12-27 21:37:50 -05:00
// note: we never do anything with the result of this so we can safely return null
return null ;
}
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( ) ;
2019-01-02 19:32:39 -05:00
2020-11-27 22:52:52 -05:00
// grab the context version of the entity
var entity = context . Clients
. First ( client = > client . ClientId = = temporalClient . ClientId ) ;
2019-06-13 20:10:08 -04:00
2020-11-27 22:52:52 -05:00
if ( temporalClient . LastConnection > entity . LastConnection )
{
entity . LastConnection = temporalClient . LastConnection ;
}
2019-06-12 11:27:15 -04:00
2020-11-27 22:52:52 -05:00
if ( temporalClient . Connections > entity . Connections )
{
entity . Connections = temporalClient . Connections ;
}
2019-06-13 20:10:08 -04:00
2020-11-27 22:52:52 -05:00
entity . Masked = temporalClient . Masked ;
2019-06-13 20:10:08 -04:00
2020-11-27 22:52:52 -05:00
if ( temporalClient . TotalConnectionTime > entity . TotalConnectionTime )
{
entity . TotalConnectionTime = temporalClient . TotalConnectionTime ;
}
2019-06-13 20:10:08 -04:00
2020-11-27 22:52:52 -05:00
if ( temporalClient . Password ! = null )
{
entity . Password = temporalClient . Password ;
}
2017-11-29 19:35:50 -05:00
2020-11-27 22:52:52 -05:00
if ( temporalClient . PasswordSalt ! = null )
{
entity . PasswordSalt = temporalClient . PasswordSalt ;
2017-11-25 20:29:58 -05:00
}
2020-11-27 22:52:52 -05:00
// update in database
await context . SaveChangesAsync ( ) ;
2021-03-22 12:09:25 -04:00
return entity . ToPartialClient ( ) ;
2017-11-25 20:29:58 -05:00
}
2018-09-16 16:34:16 -04:00
#region ServiceSpecific
2017-11-25 20:29:58 -05:00
public async Task < IList < EFClient > > GetOwners ( )
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
return await context . Clients
. Where ( c = > c . Level = = Permission . Owner )
2021-03-22 12:09:25 -04:00
. Select ( c = > c . ToPartialClient ( ) )
2020-11-27 22:52:52 -05:00
. ToListAsync ( ) ;
}
public async Task < bool > HasOwnerAsync ( CancellationToken token )
{
await using var context = _contextFactory . CreateContext ( false ) ;
return await context . Clients . AnyAsync ( client = > client . Level = = Permission . Owner , token ) ;
2017-11-25 20:29:58 -05:00
}
2019-03-24 22:34:20 -04:00
/// <summary>
/// retrieves the number of owners
/// (client level is owner)
/// </summary>
/// <returns></returns>
2020-05-16 12:54:01 -04:00
public virtual async Task < int > GetOwnerCount ( )
2019-03-24 22:34:20 -04:00
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
return await context . Clients
. CountAsync ( _client = > _client . Level = = Permission . Owner ) ;
2019-03-24 22:34:20 -04:00
}
2019-08-02 19:04:34 -04:00
public async Task < EFClient > GetClientForLogin ( int clientId )
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
return await context . Clients
. Select ( _client = > new EFClient ( )
{
NetworkId = _client . NetworkId ,
ClientId = _client . ClientId ,
CurrentAlias = new EFAlias ( )
2019-08-02 19:04:34 -04:00
{
2020-11-27 22:52:52 -05:00
Name = _client . CurrentAlias . Name
} ,
Password = _client . Password ,
PasswordSalt = _client . PasswordSalt ,
Level = _client . Level
} )
. FirstAsync ( _client = > _client . ClientId = = clientId ) ;
2019-08-02 19:04:34 -04:00
}
2019-05-03 21:13:51 -04:00
public async Task < List < EFClient > > GetPrivilegedClients ( bool includeName = true )
2018-02-16 23:24:03 -05:00
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
var iqClients = from client in context . Clients . AsNoTracking ( )
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 ,
LastConnection = client . LastConnection
} ;
return await iqClients . ToListAsync ( ) ;
2017-11-25 20:29:58 -05:00
}
2019-04-25 22:05:35 -04:00
public async Task < IList < PlayerInfo > > FindClientsByIdentifier ( string identifier )
2018-02-11 20:17:20 -05:00
{
2021-03-31 12:20:32 -04:00
var trimmedIdentifier = identifier ? . Trim ( ) ;
2021-09-05 11:43:48 -04:00
if ( trimmedIdentifier ? . Length < _appConfig . MinimumNameLength )
2018-10-15 20:51:04 -04:00
{
2019-04-25 22:05:35 -04:00
return new List < PlayerInfo > ( ) ;
2018-10-15 20:51:04 -04:00
}
2018-04-21 18:18:20 -04:00
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
long? networkId = null ;
try
2018-09-11 15:28:37 -04:00
{
2021-03-31 12:20:32 -04:00
networkId = trimmedIdentifier . ConvertGuidToLong ( System . Globalization . NumberStyles . HexNumber ) ;
2020-11-27 22:52:52 -05:00
}
catch { }
2018-09-11 15:28:37 -04:00
2021-03-31 12:20:32 -04:00
int? ipAddress = trimmedIdentifier . ConvertToIP ( ) ;
2019-04-25 22:05:35 -04:00
2020-11-27 22:52:52 -05:00
IQueryable < EFAlias > iqLinkIds = context . Aliases . Where ( _alias = > _alias . Active ) ;
2019-04-25 22:05:35 -04:00
2020-11-27 22:52:52 -05:00
// we want to query for the IP ADdress
if ( ipAddress ! = null )
{
iqLinkIds = iqLinkIds . Where ( _alias = > _alias . IPAddress = = ipAddress ) ;
}
2018-09-11 15:28:37 -04:00
2020-11-27 22:52:52 -05:00
// want to find them by name (wildcard)
else
{
2021-03-31 12:20:32 -04:00
iqLinkIds = iqLinkIds . Where ( _alias = > EF . Functions . Like ( ( _alias . SearchableName ? ? _alias . Name . ToLower ( ) ) , $"%{trimmedIdentifier.ToLower()}%" ) ) ;
2020-11-27 22:52:52 -05:00
}
2018-09-11 15:28:37 -04:00
2020-11-27 22:52:52 -05:00
var linkIds = await iqLinkIds
. Select ( _alias = > _alias . LinkId )
. ToListAsync ( ) ;
2019-08-02 19:04:34 -04:00
2020-11-27 22:52:52 -05:00
// get all the clients that match the alias link or the network id
var iqClients = context . Clients
. Where ( _client = > _client . Active ) ;
2019-06-11 09:00:14 -04:00
2020-06-30 17:39:32 -04:00
2021-09-05 11:43:48 -04:00
iqClients = iqClients . Where ( _client = > networkId = = _client . NetworkId | | linkIds . Contains ( _client . AliasLinkId )
2021-09-06 12:37:15 -04:00
| | ! _appConfig . EnableImplicitAccountLinking & & _client . CurrentAlias . IPAddress ! = null & & _client . CurrentAlias . IPAddress = = ipAddress ) ;
2019-06-11 09:00:14 -04:00
2020-11-27 22:52:52 -05:00
// we want to project our results
var iqClientProjection = iqClients . OrderByDescending ( _client = > _client . LastConnection )
. Select ( _client = > new PlayerInfo ( )
2019-04-25 22:05:35 -04:00
{
2020-11-27 22:52:52 -05:00
Name = _client . CurrentAlias . Name ,
LevelInt = ( int ) _client . Level ,
LastConnection = _client . LastConnection ,
ClientId = _client . ClientId ,
} ) ;
2018-02-23 02:06:13 -05:00
2020-11-27 22:52:52 -05:00
var clients = await iqClientProjection . ToListAsync ( ) ;
// this is so we don't try to evaluate this in the linq to entities query
foreach ( var client in clients )
{
client . Level = ( ( Permission ) client . LevelInt ) . ToLocalizedLevelName ( ) ;
2018-02-23 02:06:13 -05:00
}
2020-11-27 22:52:52 -05:00
return clients ;
2018-02-23 02:06:13 -05:00
}
2017-11-25 20:29:58 -05:00
public async Task < int > GetTotalClientsAsync ( )
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
return await context . Clients
. CountAsync ( ) ;
2017-11-25 20:29:58 -05:00
}
2019-07-16 16:27:19 -04:00
2019-07-24 11:36:37 -04:00
/// <summary>
/// Returns the number of clients seen today
/// </summary>
/// <returns></returns>
public async Task < int > GetRecentClientCount ( )
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
var startOfPeriod = DateTime . UtcNow . AddHours ( - 24 ) ;
var iqQuery = context . Clients . Where ( _client = > _client . LastConnection > = startOfPeriod ) ;
2020-06-30 17:39:32 -04:00
2020-11-27 22:52:52 -05:00
return await iqQuery . CountAsync ( ) ;
2019-07-24 11:36:37 -04:00
}
2019-07-16 16:27:19 -04:00
/// <summary>
/// gets the 10 most recently added clients to IW4MAdmin
/// </summary>
/// <returns></returns>
public async Task < IList < PlayerInfo > > GetRecentClients ( )
{
2019-10-02 18:59:10 -04:00
var startOfPeriod = DateTime . UtcNow . AddHours ( - 24 ) ;
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
var iqClients = context . Clients
. Where ( _client = > _client . CurrentAlias . IPAddress ! = null )
. Where ( _client = > _client . FirstConnection > = startOfPeriod )
. OrderByDescending ( _client = > _client . FirstConnection )
. Select ( _client = > new PlayerInfo ( )
{
ClientId = _client . ClientId ,
Name = _client . CurrentAlias . Name ,
IPAddress = _client . CurrentAlias . IPAddress . ConvertIPtoString ( ) ,
LastConnection = _client . FirstConnection
} ) ;
2019-11-15 15:50:20 -05:00
2020-11-27 22:52:52 -05:00
return await iqClients . ToListAsync ( ) ;
2019-07-16 16:27:19 -04:00
}
2018-09-16 16:34:16 -04:00
#endregion
2019-08-08 16:30:06 -04:00
/// <summary>
2019-08-10 10:08:26 -04:00
/// retrieves the number of times the given client id has been reported
2019-08-08 16:30:06 -04:00
/// </summary>
/// <param name="clientId">client id to search for report counts of</param>
/// <returns></returns>
public async Task < int > GetClientReportCount ( int clientId )
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
return await context . Penalties
. Where ( _penalty = > _penalty . Active )
. Where ( _penalty = > _penalty . OffenderId = = clientId )
. Where ( _penalty = > _penalty . Type = = EFPenalty . PenaltyType . Report )
. CountAsync ( ) ;
2019-08-08 16:30:06 -04:00
}
2019-08-10 10:08:26 -04:00
/// <summary>
2021-03-23 11:34:44 -04:00
/// indicates if the given clientid can be autoflagged
2019-08-10 10:08:26 -04:00
/// </summary>
/// <param name="clientId"></param>
/// <returns></returns>
2021-03-23 11:34:44 -04:00
public async Task < bool > CanBeAutoFlagged ( int clientId )
2019-08-10 10:08:26 -04:00
{
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( false ) ;
var now = DateTime . UtcNow ;
2021-03-23 11:34:44 -04:00
var hasExistingAutoFlag = await context . Penalties
2020-11-27 22:52:52 -05:00
. Where ( _penalty = > _penalty . Active )
. Where ( _penalty = > _penalty . OffenderId = = clientId )
. Where ( _penalty = > _penalty . Type = = EFPenalty . PenaltyType . Flag )
. Where ( _penalty = > _penalty . PunisherId = = 1 )
. Where ( _penalty = > _penalty . Expires = = null | | _penalty . Expires > now )
. AnyAsync ( ) ;
2021-03-23 11:34:44 -04:00
var hasUnflag = await context . Penalties
. Where ( _penalty = > _penalty . Active )
. Where ( _penalty = > _penalty . OffenderId = = clientId )
. Where ( _penalty = > _penalty . Type = = EFPenalty . PenaltyType . Unflag )
. AnyAsync ( ) ;
return ! hasExistingAutoFlag & & ! hasUnflag ;
2019-08-10 10:08:26 -04:00
}
2019-10-11 16:26:13 -04:00
/// <summary>
/// Unlinks shared GUID account into its own separate account
/// </summary>
/// <param name="clientId"></param>
/// <returns></returns>
public async Task UnlinkClient ( int clientId )
{
2020-11-27 22:52:52 -05:00
await using var ctx = _contextFactory . CreateContext ( ) ;
var newLink = new EFAliasLink ( ) { Active = true } ;
ctx . AliasLinks . Add ( newLink ) ;
await ctx . SaveChangesAsync ( ) ;
2019-10-11 16:26:13 -04:00
2020-11-27 22:52:52 -05:00
var client = await ctx . Clients . Include ( _client = > _client . CurrentAlias )
. FirstAsync ( _client = > _client . ClientId = = clientId ) ;
client . AliasLinkId = newLink . AliasLinkId ;
client . Level = Permission . User ;
2019-10-11 16:26:13 -04:00
2021-03-22 12:09:25 -04:00
await ctx . Aliases . Where ( _alias = > _alias . IPAddress = = client . CurrentAlias . IPAddress & & _alias . IPAddress ! = null )
2020-11-27 22:52:52 -05:00
. ForEachAsync ( _alias = > _alias . LinkId = newLink . AliasLinkId ) ;
2019-10-11 16:26:13 -04:00
2020-11-27 22:52:52 -05:00
await ctx . SaveChangesAsync ( ) ;
2019-10-11 16:26:13 -04:00
}
2020-05-24 22:22:26 -04:00
/// <summary>
/// find clients matching the given query
/// </summary>
/// <param name="query">query filters</param>
/// <returns></returns>
public async Task < ResourceQueryHelperResult < FindClientResult > > QueryResource ( FindClientRequest query )
{
var result = new ResourceQueryHelperResult < FindClientResult > ( ) ;
2020-11-27 22:52:52 -05:00
await using var context = _contextFactory . CreateContext ( enableTracking : false ) ;
2020-05-24 22:22:26 -04:00
2021-03-27 19:54:25 -04:00
IQueryable < Data . Models . Client . EFClient > iqClients = null ;
2020-05-24 22:22:26 -04:00
if ( ! string . IsNullOrEmpty ( query . Xuid ) )
{
2021-03-27 19:54:25 -04:00
var networkId = query . Xuid . ConvertGuidToLong ( System . Globalization . NumberStyles . HexNumber ) ;
iqClients = context . Clients . Where ( _client = > _client . NetworkId = = networkId ) ;
2020-05-24 22:22:26 -04:00
}
else if ( ! string . IsNullOrEmpty ( query . Name ) )
{
2021-03-22 12:09:25 -04:00
iqClients = context . Clients
2021-03-27 19:54:25 -04:00
. Where ( _client = >
EF . Functions . Like ( _client . CurrentAlias . Name . ToLower ( ) , $"%{query.Name.ToLower()}%" ) ) ;
2020-05-24 22:22:26 -04:00
}
if ( query . Direction = = SortDirection . Ascending )
{
iqClients = iqClients . OrderBy ( _client = > _client . LastConnection ) ;
}
else
{
iqClients = iqClients . OrderByDescending ( _client = > _client . LastConnection ) ;
}
var queryResults = await iqClients
. Select ( _client = > new FindClientResult
{
ClientId = _client . ClientId ,
Xuid = _client . NetworkId . ToString ( "X" ) ,
Name = _client . CurrentAlias . Name
} )
. Skip ( query . Offset )
. Take ( query . Count )
. ToListAsync ( ) ;
result . TotalResultCount = await iqClients . CountAsync ( ) ;
result . Results = queryResults ;
result . RetrievedResultCount = queryResults . Count ;
return result ;
}
2017-11-25 20:29:58 -05:00
}
}