User synchronization revamped.
This commit is contained in:
263
code/MessengerApi/Handlers/UserConfigHandler.cs
Normal file
263
code/MessengerApi/Handlers/UserConfigHandler.cs
Normal file
@ -0,0 +1,263 @@
|
||||
using MessengerApi.Configuration.Model;
|
||||
using MessengerApi.Contracts.Factories;
|
||||
using MessengerApi.Db.Entities;
|
||||
using MessengerApi.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
|
||||
namespace MessengerApi.Handlers
|
||||
{
|
||||
public class UserConfigHandler
|
||||
{
|
||||
private readonly MessengerConfiguration configuration;
|
||||
private readonly ILogger logger;
|
||||
private readonly IDbContextFactory dbContextFactory;
|
||||
|
||||
public UserConfigHandler(
|
||||
MessengerConfiguration configuration,
|
||||
ILogger logger,
|
||||
IDbContextFactory dbContextFactory)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.logger = logger;
|
||||
this.dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<UserIngestionItem[]> GetItemsFromFile(FileInfo file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
throw new InvalidOperationException("No file provided.");
|
||||
}
|
||||
else if (File.Exists(file.FullName) == false)
|
||||
{
|
||||
throw new FileNotFoundException($"Userfile doesn't exist: {this.configuration.UsersConfig.FullName}.");
|
||||
}
|
||||
|
||||
var lines = await File.ReadAllLinesAsync(file.FullName, Encoding.UTF8);
|
||||
var collection = new List<Tuple<UserIngestionItem, string>>();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var columns = line.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var item = new UserIngestionItem();
|
||||
item.UserName = columns[0];
|
||||
item.IsEnabled = bool.Parse(columns[1]);
|
||||
item.Id = Guid.Parse(columns[2]);
|
||||
item.ApiKey = Guid.Parse(columns[3]);
|
||||
|
||||
var recipients = columns.Length == 5
|
||||
? columns[4]
|
||||
: null;
|
||||
|
||||
collection.Add(new Tuple<UserIngestionItem, string>(item, recipients));
|
||||
|
||||
this.logger.Trace($"Reading user {item.UserName} with ID {item.Id}");
|
||||
}
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if(item.Item2 == null)
|
||||
{
|
||||
item.Item1.CanSendTo = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
var names = item.Item2.Split(
|
||||
',',
|
||||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
item.Item1.CanSendTo = names
|
||||
.Select(x => collection.Single(c => c.Item1.UserName == x).Item1)
|
||||
.ToArray();
|
||||
|
||||
this.logger.Trace($"User {item.Item1.UserName} has buddies {item.Item2}.");
|
||||
}
|
||||
|
||||
return collection.Select(x => x.Item1).ToArray();
|
||||
}
|
||||
|
||||
public Task<UserIngestionOperation[]> GetOperationsFromItems(UserIngestionItem[] fileItems)
|
||||
{
|
||||
if(fileItems == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileItems));
|
||||
}
|
||||
|
||||
using var context = this.dbContextFactory.CreateDbContext();
|
||||
|
||||
var existingUsers = context.Users.ToList();
|
||||
var existingRoutes = context.UserRoutes.Include(x => x.From).Include(x => x.To).ToList();
|
||||
|
||||
var collection = new List<UserIngestionOperation>();
|
||||
|
||||
foreach (var item in fileItems)
|
||||
{
|
||||
var dbItem = context.Users.SingleOrDefault(x => x.Id == item.Id);
|
||||
|
||||
if (dbItem == null)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Type = UserIngestionOperationTypes.Create,
|
||||
Subtype = UserIngestionOperationSubtypes.AddUser,
|
||||
Username = item.UserName,
|
||||
Operation = (c) =>
|
||||
{
|
||||
c.Users.Local.Add(new User
|
||||
{
|
||||
Id = item.Id,
|
||||
ApiKey = item.ApiKey,
|
||||
IsEnabled = item.IsEnabled,
|
||||
Name = item.UserName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if(dbItem.ApiKey != item.ApiKey)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Type = UserIngestionOperationTypes.Update,
|
||||
Subtype = UserIngestionOperationSubtypes.ChangeUserApiKey,
|
||||
Username = item.UserName,
|
||||
Operation = (c) =>
|
||||
{
|
||||
c.Users.Single(x => x.Id == item.ApiKey).ApiKey = item.ApiKey;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(dbItem.IsEnabled != item.IsEnabled)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Type = UserIngestionOperationTypes.Update,
|
||||
Subtype = UserIngestionOperationSubtypes.ChangeUserIsEnabled,
|
||||
Username = item.UserName,
|
||||
Operation = (c) =>
|
||||
{
|
||||
c.Users.Single(x => x.Id == item.ApiKey).IsEnabled = item.IsEnabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (dbItem.Name != item.UserName)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Type = UserIngestionOperationTypes.Update,
|
||||
Subtype = UserIngestionOperationSubtypes.ChangeUserName,
|
||||
Username = item.UserName,
|
||||
Operation = (c) =>
|
||||
{
|
||||
c.Users.Single(x => x.Id == item.ApiKey).Name = item.UserName;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.Trace($"Processed line with {item.UserName}.");
|
||||
}
|
||||
|
||||
this.logger.Debug("Finished evaluating add/update users, doing routes.");
|
||||
|
||||
foreach(var item in fileItems)
|
||||
{
|
||||
foreach(var com in item.CanSendTo)
|
||||
{
|
||||
this.logger.Trace($"Validating if {item.UserName} can send to {com.UserName}.");
|
||||
|
||||
var dbRoute = context.UserRoutes
|
||||
.Include(x => x.From)
|
||||
.Include(x => x.To)
|
||||
.SingleOrDefault(x => x.From.Id == item.Id && x.To.Id == com.Id);
|
||||
|
||||
if(dbRoute == null)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Type = UserIngestionOperationTypes.Create,
|
||||
Subtype = UserIngestionOperationSubtypes.AddUserRoute,
|
||||
Username = item.UserName,
|
||||
Operation = (c) =>
|
||||
{
|
||||
var from = c.Users
|
||||
.SingleOrDefault(x => x.Id == item.Id) ??
|
||||
c.Users.Local.Single(x => x.Id == item.Id);
|
||||
|
||||
var to = c.Users
|
||||
.SingleOrDefault(x => x.Id == com.Id) ??
|
||||
c.Users.Local.Single(x => x.Id == com.Id);
|
||||
|
||||
c.Add(new UserRoute
|
||||
{
|
||||
From = from,
|
||||
To = to
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.Debug("Finished evaluating add userroutes, doing user deletes.");
|
||||
|
||||
foreach(var existingDbUser in existingUsers)
|
||||
{
|
||||
if(fileItems.Any(x=>x.Id == existingDbUser.Id) == false)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Username = existingDbUser.Name,
|
||||
Type = UserIngestionOperationTypes.Delete,
|
||||
Subtype = UserIngestionOperationSubtypes.RemoveUser,
|
||||
Operation = (c) =>
|
||||
{
|
||||
c.Remove(c.Users.Single(x => x.Id == existingDbUser.Id));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var existingRoute in existingRoutes)
|
||||
{
|
||||
if (fileItems.Any(fi => fi.CanSendTo.Any(fic => fi.Id == existingRoute.From.Id && fic.Id == existingRoute.To.Id)) == false)
|
||||
{
|
||||
collection.Add(new UserIngestionOperation
|
||||
{
|
||||
Username = existingRoute.From.Name,
|
||||
Type = UserIngestionOperationTypes.Delete,
|
||||
Subtype = UserIngestionOperationSubtypes.RemoveUserRoute,
|
||||
Operation = (c) =>
|
||||
{
|
||||
c.Remove(c.UserRoutes.Single(x => x.Id == existingRoute.Id));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(collection.ToArray());
|
||||
}
|
||||
|
||||
public async Task UpdateFromFile(FileInfo file)
|
||||
{
|
||||
var fileItems = await this.GetItemsFromFile(file);
|
||||
var operations = await this.GetOperationsFromItems(fileItems);
|
||||
using var context = this.dbContextFactory.CreateDbContext();
|
||||
|
||||
foreach(var operation in operations)
|
||||
{
|
||||
this.logger.Info(operation.ToString());
|
||||
operation.Operation(context);
|
||||
}
|
||||
|
||||
if(operations.Any())
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
using MessengerApi.Configuration.Model;
|
||||
using MessengerApi.Contracts.Factories;
|
||||
using MessengerApi.Db;
|
||||
using MessengerApi.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace MessengerApi.Handlers
|
||||
{
|
||||
// TODO: This needs to be redone, because at every run, it wipes users and creates new ones. This makes
|
||||
// all existing DB messages unassignable.
|
||||
public class UserSetupHandler
|
||||
{
|
||||
private readonly MessengerConfiguration configuration;
|
||||
private readonly ILogger logger;
|
||||
private readonly IDbContextFactory dbContextFactory;
|
||||
|
||||
public UserSetupHandler(
|
||||
MessengerConfiguration configuration,
|
||||
ILogger logger,
|
||||
IDbContextFactory dbContextFactory)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.logger = logger;
|
||||
this.dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task UpdateFromFile(FileInfo file)
|
||||
{
|
||||
if(file.Exists)
|
||||
{
|
||||
var lines = await File.ReadAllLinesAsync(file.FullName, Encoding.UTF8);
|
||||
var items = await this.ReadLines(lines);
|
||||
|
||||
await this.SynchronizeUsers(items);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<UserSetupItem[]> ReadLines(string[] lines)
|
||||
{
|
||||
var items = new List<UserSetupItem>();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var values = line.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var item = new UserSetupItem
|
||||
{
|
||||
UserName = values[0],
|
||||
ApiKey = values[1],
|
||||
};
|
||||
|
||||
if(values.Length > 2)
|
||||
{
|
||||
item.CanSendToUserNames = values[2].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
}
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
if (items.GroupBy(x => x.UserName).Any(x => x.Count() > 1))
|
||||
{
|
||||
throw new InvalidOperationException("Usernames are not unique. One username per line.");
|
||||
}
|
||||
else if(items.GroupBy(x=>x.ApiKey).Any(x=>x.Count() > 1))
|
||||
{
|
||||
throw new InvalidOperationException("API keys are not unique. One API key per line.");
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
private Task SynchronizeUsers(IEnumerable<UserSetupItem> users)
|
||||
{
|
||||
using var db = this.dbContextFactory.CreateDbContext();
|
||||
db.RemoveRange(db.Users);
|
||||
db.RemoveRange(db.UserRoutes);
|
||||
|
||||
var dbUsers = users.Select(x => new Db.Entities.User
|
||||
{
|
||||
Id = new Guid(),
|
||||
Name = x.UserName,
|
||||
ApiKey = Guid.Parse(x.ApiKey),
|
||||
IsEnabled = true
|
||||
});
|
||||
|
||||
var dbRoutes = users.SelectMany(x => x.CanSendToUserNames.Select(cs => new Db.Entities.UserRoute
|
||||
{
|
||||
Id = new Guid(),
|
||||
From = dbUsers.Single(dbu => dbu.Name == x.UserName),
|
||||
To = dbUsers.Single(dbu => dbu.Name == x.UserName)
|
||||
}));
|
||||
|
||||
db.AddRange(dbUsers);
|
||||
db.AddRange(dbRoutes);
|
||||
|
||||
db.SaveChanges();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user