Compare commits
1 Commits
ff4c352dee
...
6fd51f4b7a
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fd51f4b7a |
@@ -1,20 +1,19 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data
|
namespace OwnChar.Data;
|
||||||
|
|
||||||
|
public interface IDataManager
|
||||||
{
|
{
|
||||||
public interface IDataManager
|
// Login
|
||||||
{
|
abstract UserAccount? Login(string username, string password);
|
||||||
// Login
|
abstract bool Logout(UserAccount? account);
|
||||||
abstract UserAccount? Login(string username, string password);
|
|
||||||
abstract bool Logout(UserAccount? account);
|
|
||||||
|
|
||||||
// User management
|
// User management
|
||||||
abstract UserAccount? CreateUserAccount(string username, string password);
|
abstract UserAccount? CreateUserAccount(string username, string password);
|
||||||
abstract UserProfile? GetUserProfile(UserAccount account);
|
abstract UserProfile? GetUserProfile(UserAccount account);
|
||||||
abstract bool DeleteUserAccount(UserAccount account);
|
abstract bool DeleteUserAccount(UserAccount account);
|
||||||
|
|
||||||
// Group management
|
// Group management
|
||||||
abstract UserProfile? GetOwner(Group group);
|
abstract UserProfile? GetOwner(Group group);
|
||||||
abstract IEnumerable<UserProfile>? GetMembers(Group group);
|
abstract IEnumerable<UserProfile>? GetMembers(Group group);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data
|
namespace OwnChar.Data;
|
||||||
|
|
||||||
|
public interface IDataProvider
|
||||||
{
|
{
|
||||||
public interface IDataProvider
|
// General
|
||||||
{
|
abstract bool IsInitialized();
|
||||||
// General
|
|
||||||
abstract bool IsInitialized();
|
|
||||||
|
|
||||||
// Model
|
// Model
|
||||||
abstract T? Create<T>() where T : class, IOwnCharObject;
|
abstract T? Create<T>() where T : class, IOwnCharObject;
|
||||||
abstract bool Save<T>(T obj) where T : class, IOwnCharObject;
|
abstract bool Save<T>(T obj) where T : class, IOwnCharObject;
|
||||||
abstract bool Delete<T>(T obj) where T : class, IOwnCharObject;
|
abstract bool Delete<T>(T obj) where T : class, IOwnCharObject;
|
||||||
|
|
||||||
// Hierarchy
|
// Hierarchy
|
||||||
abstract bool SetParent(UserProfile profile, UserAccount parent);
|
abstract bool SetParent(UserProfile profile, UserAccount parent);
|
||||||
abstract bool SetParent(Character character, Group parent);
|
abstract bool SetParent(Character character, Group parent);
|
||||||
abstract bool SetParent(Property property, Character character);
|
abstract bool SetParent(Property property, Character character);
|
||||||
abstract bool SetOwner(Group group, UserProfile owner);
|
abstract bool SetOwner(Group group, UserProfile owner);
|
||||||
abstract bool SetOwner(Character group, UserProfile owner);
|
abstract bool SetOwner(Character group, UserProfile owner);
|
||||||
|
|
||||||
// Gets
|
// Gets
|
||||||
abstract UserAccount? GetUserAccount(string username, string password);
|
abstract UserAccount? GetUserAccount(string username, string password);
|
||||||
abstract UserProfile? GetUserProfile(string username);
|
abstract UserProfile? GetUserProfile(string username);
|
||||||
abstract IEnumerable<UserProfile>? GetGroupMembers(Group group);
|
abstract IEnumerable<UserProfile>? GetGroupMembers(Group group);
|
||||||
abstract UserProfile? GetOwner(Group group);
|
abstract UserProfile? GetOwner(Group group);
|
||||||
abstract UserProfile? GetOwner(Character character);
|
abstract UserProfile? GetOwner(Character character);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
namespace OwnChar.Data.Managers
|
namespace OwnChar.Data.Managers;
|
||||||
|
|
||||||
|
public class ClientDataManager
|
||||||
{
|
{
|
||||||
public class ClientDataManager
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,78 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Managers
|
namespace OwnChar.Data.Managers;
|
||||||
|
|
||||||
|
public class DefaultDataManager(IDataProvider dataProvider) : IDataManager
|
||||||
{
|
{
|
||||||
public class DefaultDataManager(IDataProvider dataProvider) : IDataManager
|
private const string defaultUsername = "admin";
|
||||||
|
private const string defaultPassword = "admin";
|
||||||
|
|
||||||
|
public IDataProvider DataProvider { get; } = dataProvider;
|
||||||
|
|
||||||
|
public IEnumerable<UserProfile>? GetMembers(Group group)
|
||||||
{
|
{
|
||||||
private const string defaultUsername = "admin";
|
return DataProvider.GetGroupMembers(group);
|
||||||
private const string defaultPassword = "admin";
|
}
|
||||||
|
|
||||||
public IDataProvider DataProvider { get; } = dataProvider;
|
public UserProfile? GetOwner(Group group)
|
||||||
|
{
|
||||||
|
return DataProvider.GetOwner(group);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<UserProfile>? GetMembers(Group group)
|
public UserProfile? GetUserProfile(UserAccount account)
|
||||||
{
|
{
|
||||||
return DataProvider.GetGroupMembers(group);
|
ArgumentException.ThrowIfNullOrWhiteSpace(account.Username, nameof(account.Username));
|
||||||
}
|
return DataProvider.GetUserProfile(account.Username);
|
||||||
|
}
|
||||||
|
|
||||||
public UserProfile? GetOwner(Group group)
|
public UserAccount? Login(string username, string password)
|
||||||
{
|
{
|
||||||
return DataProvider.GetOwner(group);
|
return DataProvider.GetUserAccount(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserProfile? GetUserProfile(UserAccount account)
|
public bool Logout(UserAccount? account)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(account.Username, nameof(account.Username));
|
return true;
|
||||||
return DataProvider.GetUserProfile(account.Username);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public UserAccount? Login(string username, string password)
|
public bool Initialize(bool force)
|
||||||
{
|
{
|
||||||
return DataProvider.GetUserAccount(username, password);
|
if (force || !DataProvider.IsInitialized())
|
||||||
}
|
return CreateUserAccount(defaultUsername, Utils.HashPassword(defaultUsername, defaultPassword)) != null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Logout(UserAccount? account)
|
public UserAccount? CreateUserAccount(string username, string password)
|
||||||
{
|
{
|
||||||
return true;
|
var account = DataProvider.Create<UserAccount>();
|
||||||
}
|
var profile = DataProvider.Create<UserProfile>();
|
||||||
|
var group = DataProvider.Create<Group>();
|
||||||
|
|
||||||
public bool Initialize(bool force)
|
ArgumentNullException.ThrowIfNull(account, nameof(account));
|
||||||
{
|
ArgumentNullException.ThrowIfNull(profile, nameof(profile));
|
||||||
if (force || !DataProvider.IsInitialized())
|
ArgumentNullException.ThrowIfNull(group, nameof(group));
|
||||||
return CreateUserAccount(defaultUsername, Utils.HashPassword(defaultUsername, defaultPassword)) != null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAccount? CreateUserAccount(string username, string password)
|
account.Username = username;
|
||||||
{
|
account.Password = password;
|
||||||
var account = DataProvider.Create<UserAccount>();
|
|
||||||
var profile = DataProvider.Create<UserProfile>();
|
|
||||||
var group = DataProvider.Create<Group>();
|
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(account, nameof(account));
|
profile.Name = username;
|
||||||
ArgumentNullException.ThrowIfNull(profile, nameof(profile));
|
DataProvider.SetParent(profile, account);
|
||||||
ArgumentNullException.ThrowIfNull(group, nameof(group));
|
|
||||||
|
|
||||||
account.Username = username;
|
group.IsInternal = true;
|
||||||
account.Password = password;
|
DataProvider.SetOwner(group, profile);
|
||||||
|
|
||||||
profile.Name = username;
|
DataProvider.Save(account);
|
||||||
DataProvider.SetParent(profile, account);
|
DataProvider.Save(profile);
|
||||||
|
DataProvider.Save(group);
|
||||||
|
|
||||||
group.IsInternal = true;
|
return account;
|
||||||
DataProvider.SetOwner(group, profile);
|
}
|
||||||
|
|
||||||
DataProvider.Save(account);
|
public bool DeleteUserAccount(UserAccount account)
|
||||||
DataProvider.Save(profile);
|
{
|
||||||
DataProvider.Save(group);
|
if (!string.IsNullOrWhiteSpace(account.Username) && DataProvider.GetUserProfile(account.Username) is UserProfile userProfile)
|
||||||
|
userProfile.Name = "Deleted user";
|
||||||
return account;
|
DataProvider.Delete(account);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
public bool DeleteUserAccount(UserAccount account)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(account.Username) && DataProvider.GetUserProfile(account.Username) is UserProfile userProfile)
|
|
||||||
userProfile.Name = "Deleted user";
|
|
||||||
DataProvider.Delete(account);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using OwnChar.Data.Providers.JsonFile.Model;
|
using OwnChar.Data.Providers.JsonFile.Model;
|
||||||
using OwnChar.Model;
|
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile
|
namespace OwnChar.Data.Providers.JsonFile;
|
||||||
|
|
||||||
|
public class JsonFile
|
||||||
{
|
{
|
||||||
public class JsonFile
|
public List<JsonUserAccount> UserAccounts { get; } = [];
|
||||||
{
|
public List<JsonCharacter> Characters { get; } = [];
|
||||||
public List<JsonUserAccount> UserAccounts { get; } = [];
|
public List<JsonGroup> Groups { get; } = [];
|
||||||
public List<JsonCharacter> Characters { get; } = [];
|
|
||||||
public List<JsonGroup> Groups { get; } = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,221 +1,219 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OwnChar.Data.Providers.JsonFile.Model;
|
using OwnChar.Data.Providers.JsonFile.Model;
|
||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
using System.Net.NetworkInformation;
|
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile
|
namespace OwnChar.Data.Providers.JsonFile;
|
||||||
|
|
||||||
|
public class JsonFileDataProvider : IDataProvider
|
||||||
{
|
{
|
||||||
public class JsonFileDataProvider : IDataProvider
|
public JsonFile JsonFile { get; protected set; }
|
||||||
|
public string JsonFilePath { get; protected set; }
|
||||||
|
|
||||||
|
public JsonFileDataProvider(string filePath)
|
||||||
{
|
{
|
||||||
public JsonFile JsonFile { get; protected set; }
|
JsonFilePath = filePath;
|
||||||
public string JsonFilePath { get; protected set; }
|
LoadFile();
|
||||||
|
|
||||||
public JsonFileDataProvider(string filePath)
|
// Get rid of bad compiler warnings
|
||||||
|
if (JsonFile == null)
|
||||||
|
throw new Exception("Something went incredible wrong at initialization of the JsonFile.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void LoadFile()
|
||||||
|
{
|
||||||
|
if (File.Exists(JsonFilePath) && JsonConvert.DeserializeObject<JsonFile>(File.ReadAllText(JsonFilePath), CreateJsonSerializerSettings()) is JsonFile jsonFile)
|
||||||
|
JsonFile = jsonFile;
|
||||||
|
JsonFile ??= new();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SaveFile()
|
||||||
|
{
|
||||||
|
File.WriteAllText(JsonFilePath, JsonConvert.SerializeObject(JsonFile, CreateJsonSerializerSettings()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JsonSerializerSettings CreateJsonSerializerSettings()
|
||||||
|
{
|
||||||
|
return new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
JsonFilePath = filePath;
|
PreserveReferencesHandling = PreserveReferencesHandling.All,
|
||||||
LoadFile();
|
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
||||||
|
TypeNameHandling = TypeNameHandling.Auto,
|
||||||
|
Formatting = Formatting.Indented,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public T? Create<T>() where T : class, IOwnCharObject
|
||||||
|
{
|
||||||
|
var t = typeof(T);
|
||||||
|
IOwnCharObject? obj;
|
||||||
|
|
||||||
// Get rid of bad compiler warnings
|
if (t == typeof(Property))
|
||||||
if (JsonFile == null)
|
obj = new JsonProp();
|
||||||
throw new Exception("Something went incredible wrong at initialization of the JsonFile.");
|
else if (t == typeof(PropertyCategory))
|
||||||
|
obj = new JsonPropCat();
|
||||||
|
else if (t == typeof(Character))
|
||||||
|
obj = new JsonCharacter();
|
||||||
|
else if (t == typeof(Group))
|
||||||
|
obj = new JsonGroup();
|
||||||
|
else if (t == typeof(UserAccount))
|
||||||
|
obj = new JsonUserAccount();
|
||||||
|
else if (t == typeof(UserProfile))
|
||||||
|
obj = new JsonUserProfile();
|
||||||
|
else
|
||||||
|
obj = null;
|
||||||
|
|
||||||
|
return obj as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Save<T>(T obj) where T : class, IOwnCharObject
|
||||||
|
{
|
||||||
|
if (obj is JsonCharacter character)
|
||||||
|
{
|
||||||
|
if (!JsonFile.Characters.Contains(character))
|
||||||
|
JsonFile.Characters.Add(character);
|
||||||
|
}
|
||||||
|
else if (obj is JsonGroup group)
|
||||||
|
{
|
||||||
|
if (!JsonFile.Groups.Contains(group))
|
||||||
|
JsonFile.Groups.Add(group);
|
||||||
|
}
|
||||||
|
else if (obj is JsonUserAccount account)
|
||||||
|
{
|
||||||
|
if (!JsonFile.UserAccounts.Contains(account))
|
||||||
|
JsonFile.UserAccounts.Add(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void LoadFile()
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInitialized()
|
||||||
|
{
|
||||||
|
return JsonFile.UserAccounts.Count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete<T>(T obj) where T : class, IOwnCharObject
|
||||||
|
{
|
||||||
|
if (obj is JsonCharacter character)
|
||||||
{
|
{
|
||||||
if (File.Exists(JsonFilePath) && JsonConvert.DeserializeObject<JsonFile>(File.ReadAllText(JsonFilePath), CreateJsonSerializerSettings()) is JsonFile jsonFile)
|
JsonFile.Groups.ForEach(n => n.Characters.Remove(character));
|
||||||
JsonFile = jsonFile;
|
|
||||||
JsonFile ??= new();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void SaveFile()
|
|
||||||
{
|
|
||||||
File.WriteAllText(JsonFilePath, JsonConvert.SerializeObject(JsonFile, CreateJsonSerializerSettings()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected JsonSerializerSettings CreateJsonSerializerSettings()
|
|
||||||
{
|
|
||||||
return new JsonSerializerSettings
|
|
||||||
{
|
|
||||||
PreserveReferencesHandling = PreserveReferencesHandling.All,
|
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
|
||||||
TypeNameHandling = TypeNameHandling.Auto,
|
|
||||||
Formatting = Formatting.Indented,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
public T? Create<T>() where T : class, IOwnCharObject
|
|
||||||
{
|
|
||||||
var t = typeof(T);
|
|
||||||
IOwnCharObject? obj;
|
|
||||||
|
|
||||||
if (t == typeof(Property))
|
|
||||||
obj = new JsonProp();
|
|
||||||
else if (t == typeof(PropertyCategory))
|
|
||||||
obj = new JsonPropCat();
|
|
||||||
else if (t == typeof(Character))
|
|
||||||
obj = new JsonCharacter();
|
|
||||||
else if (t == typeof(Group))
|
|
||||||
obj = new JsonGroup();
|
|
||||||
else if (t == typeof(UserAccount))
|
|
||||||
obj = new JsonUserAccount();
|
|
||||||
else if (t == typeof(UserProfile))
|
|
||||||
obj = new JsonUserProfile();
|
|
||||||
else
|
|
||||||
obj = null;
|
|
||||||
|
|
||||||
return obj as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Save<T>(T obj) where T : class, IOwnCharObject
|
|
||||||
{
|
|
||||||
if (obj is JsonCharacter character)
|
|
||||||
{
|
|
||||||
if (!JsonFile.Characters.Contains(character))
|
|
||||||
JsonFile.Characters.Add(character);
|
|
||||||
}
|
|
||||||
else if (obj is JsonGroup group)
|
|
||||||
{
|
|
||||||
if (!JsonFile.Groups.Contains(group))
|
|
||||||
JsonFile.Groups.Add(group);
|
|
||||||
}
|
|
||||||
else if(obj is JsonUserAccount account)
|
|
||||||
{
|
|
||||||
if (!JsonFile.UserAccounts.Contains(account))
|
|
||||||
JsonFile.UserAccounts.Add(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (obj is JsonProp prop)
|
||||||
public bool IsInitialized()
|
|
||||||
{
|
{
|
||||||
return JsonFile.UserAccounts.Count != 0;
|
JsonFile.Characters.ForEach(n => n.Properties.Remove(prop));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (obj is JsonPropCat propCat)
|
||||||
public bool Delete<T>(T obj) where T : class, IOwnCharObject
|
|
||||||
{
|
{
|
||||||
if (obj is JsonCharacter character)
|
JsonFile.Characters.ForEach(n => n.Properties.ForEach(m =>
|
||||||
{
|
{
|
||||||
JsonFile.Groups.ForEach(n => n.Characters.Remove(character));
|
if (m.Category == propCat)
|
||||||
return true;
|
m.Category = null;
|
||||||
}
|
}));
|
||||||
else if (obj is JsonProp prop)
|
JsonFile.Characters.ForEach(n => n.PropertyCategories.Remove(propCat));
|
||||||
{
|
return true;
|
||||||
JsonFile.Characters.ForEach(n => n.Properties.Remove(prop));
|
}
|
||||||
return true;
|
else if (obj is JsonGroup group)
|
||||||
}
|
{
|
||||||
else if (obj is JsonPropCat propCat)
|
JsonFile.Groups.Remove(group);
|
||||||
{
|
return true;
|
||||||
JsonFile.Characters.ForEach(n => n.Properties.ForEach(m =>
|
}
|
||||||
{
|
else if (obj is JsonUserAccount account)
|
||||||
if (m.Category == propCat)
|
{
|
||||||
m.Category = null;
|
JsonFile.UserAccounts.Remove(account);
|
||||||
}));
|
return true;
|
||||||
JsonFile.Characters.ForEach(n => n.PropertyCategories.Remove(propCat));
|
}
|
||||||
return true;
|
else if (obj is JsonUserProfile profile)
|
||||||
}
|
{
|
||||||
else if (obj is JsonGroup group)
|
// We don't delete profiles at the moment!
|
||||||
{
|
profile.Name = "Deleted user";
|
||||||
JsonFile.Groups.Remove(group);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is JsonUserAccount account)
|
|
||||||
{
|
|
||||||
JsonFile.UserAccounts.Remove(account);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is JsonUserProfile profile)
|
|
||||||
{
|
|
||||||
// We don't delete profiles at the moment!
|
|
||||||
profile.Name = "Deleted user";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetParent(UserProfile profile, UserAccount parent)
|
return false;
|
||||||
{
|
}
|
||||||
if (parent is JsonUserAccount jaccount && profile is JsonUserProfile jprofile)
|
|
||||||
{
|
|
||||||
jaccount.Profile = jprofile;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetParent(Character character, Group parent)
|
public bool SetParent(UserProfile profile, UserAccount parent)
|
||||||
|
{
|
||||||
|
if (parent is JsonUserAccount jaccount && profile is JsonUserProfile jprofile)
|
||||||
{
|
{
|
||||||
if (character is JsonCharacter jcharacter && parent is JsonGroup jgroup)
|
jaccount.Profile = jprofile;
|
||||||
{
|
return true;
|
||||||
if (!jgroup.Characters.Contains(jcharacter))
|
|
||||||
jgroup.Characters.Add(jcharacter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetParent(Property property, Character character)
|
public bool SetParent(Character character, Group parent)
|
||||||
|
{
|
||||||
|
if (character is JsonCharacter jcharacter && parent is JsonGroup jgroup)
|
||||||
{
|
{
|
||||||
if (property is JsonProp jprop && character is JsonCharacter jcharacter)
|
if (!jgroup.Characters.Contains(jcharacter))
|
||||||
{
|
jgroup.Characters.Add(jcharacter);
|
||||||
if (!jcharacter.Properties.Contains(jprop))
|
return true;
|
||||||
jcharacter.Properties.Add(jprop);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetOwner(Group group, UserProfile owner)
|
public bool SetParent(Property property, Character character)
|
||||||
|
{
|
||||||
|
if (property is JsonProp jprop && character is JsonCharacter jcharacter)
|
||||||
{
|
{
|
||||||
if (group is JsonGroup jgroup && owner is JsonUserProfile jprofile)
|
if (!jcharacter.Properties.Contains(jprop))
|
||||||
{
|
jcharacter.Properties.Add(jprop);
|
||||||
jgroup.Owner = jprofile;
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetOwner(Character character, UserProfile owner)
|
public bool SetOwner(Group group, UserProfile owner)
|
||||||
|
{
|
||||||
|
if (group is JsonGroup jgroup && owner is JsonUserProfile jprofile)
|
||||||
{
|
{
|
||||||
if (character is JsonCharacter jcharacter && owner is JsonUserProfile jprofile)
|
jgroup.Owner = jprofile;
|
||||||
{
|
return true;
|
||||||
jcharacter.Owner = jprofile;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public UserAccount? GetUserAccount(string username, string password)
|
public bool SetOwner(Character character, UserProfile owner)
|
||||||
|
{
|
||||||
|
if (character is JsonCharacter jcharacter && owner is JsonUserProfile jprofile)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
|
jcharacter.Owner = jprofile;
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
|
return true;
|
||||||
return JsonFile.UserAccounts.FirstOrDefault(n => n.Username == username && n.Password == password);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public UserProfile? GetUserProfile(string username)
|
public UserAccount? GetUserAccount(string username, string password)
|
||||||
{
|
{
|
||||||
return JsonFile.UserAccounts.FirstOrDefault(n => n.Username == username)?.Profile;
|
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
|
||||||
}
|
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
|
||||||
|
return JsonFile.UserAccounts.FirstOrDefault(n => n.Username == username && n.Password == password);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<UserProfile>? GetGroupMembers(Group group)
|
public UserProfile? GetUserProfile(string username)
|
||||||
{
|
{
|
||||||
if (group is JsonGroup jgroup)
|
return JsonFile.UserAccounts.FirstOrDefault(n => n.Username == username)?.Profile;
|
||||||
return jgroup.Members;
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserProfile? GetOwner(Group group)
|
public IEnumerable<UserProfile>? GetGroupMembers(Group group)
|
||||||
{
|
{
|
||||||
if (group is JsonGroup jgroup)
|
if (group is JsonGroup jgroup)
|
||||||
return jgroup.Owner;
|
return jgroup.Members;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserProfile? GetOwner(Character character)
|
public UserProfile? GetOwner(Group group)
|
||||||
{
|
{
|
||||||
if (character is JsonCharacter jcharacter)
|
if (group is JsonGroup jgroup)
|
||||||
return jcharacter.Owner;
|
return jgroup.Owner;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserProfile? GetOwner(Character character)
|
||||||
|
{
|
||||||
|
if (character is JsonCharacter jcharacter)
|
||||||
|
return jcharacter.Owner;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile.Model
|
namespace OwnChar.Data.Providers.JsonFile.Model;
|
||||||
|
|
||||||
|
public class JsonCharacter : Character
|
||||||
{
|
{
|
||||||
public class JsonCharacter : Character
|
public virtual JsonUserProfile? Owner { get; set; }
|
||||||
{
|
public virtual List<JsonProp> Properties { get; } = [];
|
||||||
public virtual JsonUserProfile? Owner { get; set; }
|
public virtual List<JsonPropCat> PropertyCategories { get; } = [];
|
||||||
public virtual List<JsonProp> Properties { get; } = [];
|
|
||||||
public virtual List<JsonPropCat> PropertyCategories { get; } = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile.Model
|
namespace OwnChar.Data.Providers.JsonFile.Model;
|
||||||
|
|
||||||
|
public class JsonGroup : Group
|
||||||
{
|
{
|
||||||
public class JsonGroup : Group
|
public virtual JsonUserProfile? Owner { get; set; }
|
||||||
{
|
public virtual List<JsonUserProfile> Members { get; } = [];
|
||||||
public virtual JsonUserProfile? Owner { get; set; }
|
public virtual List<JsonCharacter> Characters { get; } = [];
|
||||||
public virtual List<JsonUserProfile> Members { get; } = [];
|
|
||||||
public virtual List<JsonCharacter> Characters { get; } = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile.Model
|
namespace OwnChar.Data.Providers.JsonFile.Model;
|
||||||
|
|
||||||
|
public class JsonProp : Property
|
||||||
{
|
{
|
||||||
public class JsonProp : Property
|
public virtual JsonPropCat? Category { get; set; } = null;
|
||||||
{
|
|
||||||
public virtual JsonPropCat? Category { get; set; } = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile.Model
|
namespace OwnChar.Data.Providers.JsonFile.Model;
|
||||||
|
|
||||||
|
public class JsonPropCat : PropertyCategory
|
||||||
{
|
{
|
||||||
public class JsonPropCat : PropertyCategory
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile.Model
|
namespace OwnChar.Data.Providers.JsonFile.Model;
|
||||||
{
|
|
||||||
public class JsonUserAccount : UserAccount
|
|
||||||
{
|
|
||||||
public virtual JsonUserProfile? Profile { get; set; }
|
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
public class JsonUserAccount : UserAccount
|
||||||
public override UserType Type { get => base.Type; set => base.Type = value; }
|
{
|
||||||
}
|
public virtual JsonUserProfile? Profile { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public override UserType Type { get => base.Type; set => base.Type = value; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Data.Providers.JsonFile.Model
|
namespace OwnChar.Data.Providers.JsonFile.Model;
|
||||||
|
|
||||||
|
public class JsonUserProfile : UserProfile
|
||||||
{
|
{
|
||||||
public class JsonUserProfile : UserProfile
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Manager
|
namespace OwnChar.Manager;
|
||||||
|
|
||||||
|
public class CharacterManager(OwnCharManager manager)
|
||||||
{
|
{
|
||||||
public class CharacterManager(OwnCharManager manager)
|
public OwnCharManager Manager { get; } = manager;
|
||||||
|
|
||||||
|
public IEnumerable<Character>? GetCharacters(Group? group)
|
||||||
{
|
{
|
||||||
public OwnCharManager Manager { get; } = manager;
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Character>? GetCharacters(Group? group)
|
public IEnumerable<Character>? GetCharacters(UserProfile? profile)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Character>? GetCharacters(UserProfile? profile)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
namespace OwnChar.Manager.Exceptions
|
namespace OwnChar.Manager.Exceptions;
|
||||||
|
|
||||||
|
public class LoginException(string message) : Exception(message)
|
||||||
{
|
{
|
||||||
public class LoginException(string message) : Exception(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
|
|
||||||
namespace OwnChar.Manager
|
namespace OwnChar.Manager;
|
||||||
|
|
||||||
|
public class GroupsManager(OwnCharManager manager)
|
||||||
{
|
{
|
||||||
public class GroupsManager(OwnCharManager manager)
|
public OwnCharManager Manager { get; } = manager;
|
||||||
|
|
||||||
|
public UserProfile? GetOwner(Group? group)
|
||||||
{
|
{
|
||||||
public OwnCharManager Manager { get; } = manager;
|
ArgumentNullException.ThrowIfNull(group, nameof(group));
|
||||||
|
return Manager.DataManager?.GetOwner(group);
|
||||||
|
}
|
||||||
|
|
||||||
public UserProfile? GetOwner(Group? group)
|
public IEnumerable<UserProfile>? GetMembers(Group? group)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(group, nameof(group));
|
ArgumentNullException.ThrowIfNull(group, nameof(group));
|
||||||
return Manager.DataManager?.GetOwner(group);
|
return Manager.DataManager?.GetMembers(group);
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<UserProfile>? GetMembers(Group? group)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(group, nameof(group));
|
|
||||||
return Manager.DataManager?.GetMembers(group);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,58 +3,57 @@ using OwnChar.Manager.Exceptions;
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
using Pilz.Cryptography;
|
using Pilz.Cryptography;
|
||||||
|
|
||||||
namespace OwnChar.Manager
|
namespace OwnChar.Manager;
|
||||||
|
|
||||||
|
public class OwnCharManager
|
||||||
{
|
{
|
||||||
public class OwnCharManager
|
// User
|
||||||
|
public bool IsLoggedIn => CurrentUser != null;
|
||||||
|
public UserAccount? CurrentUser { get; private set; }
|
||||||
|
|
||||||
|
// Data Provider
|
||||||
|
public IDataManager? DataManager { get; set; }
|
||||||
|
|
||||||
|
// Manager
|
||||||
|
public UserManager Users { get; }
|
||||||
|
public GroupsManager Groups { get; }
|
||||||
|
public CharacterManager Characters { get; }
|
||||||
|
|
||||||
|
public OwnCharManager()
|
||||||
{
|
{
|
||||||
// User
|
Users = new(this);
|
||||||
public bool IsLoggedIn => CurrentUser != null;
|
Groups = new(this);
|
||||||
public UserAccount? CurrentUser { get; private set; }
|
Characters = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Data Provider
|
internal protected void CheckLogin()
|
||||||
public IDataManager? DataManager { get; set; }
|
{
|
||||||
|
if (!IsLoggedIn)
|
||||||
|
throw new LoginException("You are already logged in!");
|
||||||
|
}
|
||||||
|
|
||||||
// Manager
|
/// <summary>
|
||||||
public UserManager Users { get; }
|
/// Tries to login on the given data provider.
|
||||||
public GroupsManager Groups { get; }
|
/// </summary>
|
||||||
public CharacterManager Characters { get; }
|
/// <returns>Returns <see cref="true"/> if the login was successfull and <see cref="false"/> if not.</returns>
|
||||||
|
public bool Login(IDataManager? proxy, string? username, SecureString? password)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(proxy, nameof(proxy));
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
|
||||||
|
|
||||||
public OwnCharManager()
|
username = username.Trim().ToLower();
|
||||||
{
|
CurrentUser = proxy.Login(username, Utils.HashPassword(username, password));
|
||||||
Users = new(this);
|
|
||||||
Groups = new(this);
|
|
||||||
Characters = new(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal protected void CheckLogin()
|
return IsLoggedIn;
|
||||||
{
|
}
|
||||||
if (!IsLoggedIn)
|
|
||||||
throw new LoginException("You are already logged in!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to login on the given data provider.
|
/// Closes the session on the current data provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Returns <see cref="true"/> if the login was successfull and <see cref="false"/> if not.</returns>
|
/// <returns></returns>
|
||||||
public bool Login(IDataManager? proxy, string? username, SecureString? password)
|
public bool Logout()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(proxy, nameof(proxy));
|
return DataManager?.Logout(CurrentUser) ?? true;
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
|
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
|
|
||||||
|
|
||||||
username = username.Trim().ToLower();
|
|
||||||
CurrentUser = proxy.Login(username, Utils.HashPassword(username, password));
|
|
||||||
|
|
||||||
return IsLoggedIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closes the session on the current data provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool Logout()
|
|
||||||
{
|
|
||||||
return DataManager?.Logout(CurrentUser) ?? true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
using OwnChar.Model;
|
using OwnChar.Model;
|
||||||
using Pilz.Cryptography;
|
using Pilz.Cryptography;
|
||||||
|
|
||||||
namespace OwnChar.Manager
|
namespace OwnChar.Manager;
|
||||||
|
|
||||||
|
public class UserManager(OwnCharManager manager)
|
||||||
{
|
{
|
||||||
public class UserManager(OwnCharManager manager)
|
public OwnCharManager Manager { get; } = manager;
|
||||||
|
|
||||||
|
public UserProfile? GetOwnUserProfile()
|
||||||
{
|
{
|
||||||
public OwnCharManager Manager { get; } = manager;
|
Manager.CheckLogin();
|
||||||
|
return Manager.DataManager!.GetUserProfile(Manager.CurrentUser!);
|
||||||
|
}
|
||||||
|
|
||||||
public UserProfile? GetOwnUserProfile()
|
public UserAccount? CreateUserAccount(string? username, SecureString? password)
|
||||||
{
|
{
|
||||||
Manager.CheckLogin();
|
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
|
||||||
return Manager.DataManager!.GetUserProfile(Manager.CurrentUser!);
|
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
|
||||||
}
|
username = username.Trim().ToLower();
|
||||||
|
return Manager.DataManager?.CreateUserAccount(username, Utils.HashPassword(username, password));
|
||||||
|
}
|
||||||
|
|
||||||
public UserAccount? CreateUserAccount(string? username, SecureString? password)
|
public bool DeleteUserAccount(UserAccount? account)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
|
ArgumentNullException.ThrowIfNull(account, nameof(account));
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
|
return Manager.DataManager?.DeleteUserAccount(account) ?? false;
|
||||||
username = username.Trim().ToLower();
|
|
||||||
return Manager.DataManager?.CreateUserAccount(username, Utils.HashPassword(username, password));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DeleteUserAccount(UserAccount? account)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(account, nameof(account));
|
|
||||||
return Manager.DataManager?.DeleteUserAccount(account) ?? false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public abstract class Character : IOwnCharObject
|
||||||
{
|
{
|
||||||
public abstract class Character : IOwnCharObject
|
public virtual string? Name { get; set; }
|
||||||
{
|
|
||||||
public virtual string? Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public abstract class Group : IOwnCharObject
|
||||||
{
|
{
|
||||||
public abstract class Group : IOwnCharObject
|
public virtual string? Name { get; set; }
|
||||||
{
|
public virtual bool IsInternal { get; set; }
|
||||||
public virtual string? Name { get; set; }
|
|
||||||
public virtual bool IsInternal { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public interface IOwnCharObject
|
||||||
{
|
{
|
||||||
public interface IOwnCharObject
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public abstract class Property : IOwnCharObject
|
||||||
{
|
{
|
||||||
public abstract class Property : IOwnCharObject
|
public virtual string? Name { get; set; }
|
||||||
{
|
public virtual object? Value { get; set; }
|
||||||
public virtual string? Name { get; set; }
|
|
||||||
public virtual object? Value { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public abstract class PropertyCategory : IOwnCharObject
|
||||||
{
|
{
|
||||||
public abstract class PropertyCategory : IOwnCharObject
|
public virtual string? Name { get; set; }
|
||||||
{
|
|
||||||
public virtual string? Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public abstract class UserAccount : IOwnCharObject
|
||||||
{
|
{
|
||||||
public abstract class UserAccount : IOwnCharObject
|
public virtual string? Username { get; set; }
|
||||||
{
|
public virtual string? Password { get; set; }
|
||||||
public virtual string? Username { get; set; }
|
public virtual string? Email { get; set; }
|
||||||
public virtual string? Password { get; set; }
|
public virtual UserType Type { get; set; }
|
||||||
public virtual string? Email { get; set; }
|
|
||||||
public virtual UserType Type { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public abstract class UserProfile : IOwnCharObject
|
||||||
{
|
{
|
||||||
public abstract class UserProfile : IOwnCharObject
|
public virtual string? Name { get; set; }
|
||||||
{
|
|
||||||
public virtual string? Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
namespace OwnChar.Model
|
namespace OwnChar.Model;
|
||||||
|
|
||||||
|
public enum UserType
|
||||||
{
|
{
|
||||||
public enum UserType
|
Guest,
|
||||||
{
|
User,
|
||||||
Guest,
|
Admin
|
||||||
User,
|
|
||||||
Admin
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using Pilz.Cryptography;
|
using Pilz.Cryptography;
|
||||||
|
|
||||||
namespace OwnChar
|
namespace OwnChar;
|
||||||
|
|
||||||
|
public static class Utils
|
||||||
{
|
{
|
||||||
public static class Utils
|
public static string HashPassword(string username, SecureString password)
|
||||||
{
|
{
|
||||||
public static string HashPassword(string username, SecureString password)
|
// TODO: Implement a good hasing algorythmus (like MD5) BEFORE going productive!
|
||||||
{
|
return (username + ":" + password).GetHashCode().ToString();
|
||||||
// TODO: Implement a good hasing algorythmus (like MD5) BEFORE going productive!
|
|
||||||
return (username + ":" + password).GetHashCode().ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user