Add project data

This commit is contained in:
2025-12-30 02:22:44 +01:00
parent a6316b8b06
commit 747af63a29
2301 changed files with 67690 additions and 1 deletions

View File

@@ -0,0 +1,91 @@
using Tabletop.Core.Constants;
using Tabletop.Core.Models;
using Tabletop.Core.Services;
namespace Tabletop.Core.Calculators
{
public class Calculation
{
public static async Task<int> ForceAsync(Unit unit)
{
double unitForce = unit.Defense * 25 + unit.Moving * 5;
double primaryWeaponForce = 0;
double secondaryWeaponForce = 0;
unit.PrimaryWeapon = AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == unit.PrimaryWeaponId)?.DeepCopyByExpressionTree();
unit.SecondaryWeapon = AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == unit.SecondaryWeaponId)?.DeepCopyByExpressionTree();
unit.FirstAbility = AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == unit.FirstAbilityId)?.DeepCopyByExpressionTree();
unit.SecondAbility = AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == unit.SecondAbilityId)?.DeepCopyByExpressionTree();
if (unit.PrimaryWeapon != null)
{
primaryWeaponForce = unit.PrimaryWeapon.Attack * (unit.PrimaryWeapon.Quality + (unit.FirstAbility?.Quality ?? 0) + (unit.SecondAbility?.Quality ?? 0)) * (unit.PrimaryWeapon.Range / 10) * unit.PrimaryWeapon.Dices;
}
if (unit.SecondaryWeapon != null)
{
secondaryWeaponForce = (unit.SecondaryWeapon.Attack * unit.SecondaryWeapon.Quality + (unit.FirstAbility?.Quality ?? 0) + (unit.SecondAbility?.Quality ?? 0)) * (unit.SecondaryWeapon.Range / 10) * unit.SecondaryWeapon.Dices;
}
return await Task.FromResult(Convert.ToInt32(Math.Round((unitForce + primaryWeaponForce + secondaryWeaponForce) / 25) + (unit.FirstAbility?.Force ?? 0) + (unit.SecondAbility?.Force ?? 0)));
}
public static Task<double> RandomNumberAsync()
{
Random random = new();
double r = random.Next(1, 9);
return Task.FromResult(r);
}
public static async Task<double> ProbabilityAsync(int attackerId, int defenderId, CoverTypes cover)
{
var (value0, value1) = await AttackValueTranslatorAsync(AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId)?.Attack ?? 0, AppdataService.Units.FirstOrDefault(x => x.UnitId == defenderId)?.Defense ?? 0);
double x = ((AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId)?.Quality ?? 0) + ((AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.FirstAbilityId)?.Quality ?? 0) + ((double)(AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.SecondAbilityId)?.Quality ?? 0)))) / 8 * ((9 - (double)value0) / 8) * (AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId)?.Dices ?? 1);
if (AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId) == AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.SecondaryWeaponId))
{
x *= 2;
}
if (value1 != 0)
{
x *= (9 - (double)value1) / 8;
}
if (Convert.ToInt32(cover) == 1)
{
x *= 0.75;
}
else if (Convert.ToInt32(cover) == 2)
{
x *= 0.625;
}
return x;
}
public static Task<(int, int)> AttackValueTranslatorAsync(int attacker, int defender)
{
int x = 5;
if (attacker > defender)
{
x -= attacker - defender;
}
else if (attacker < defender)
{
x += defender - attacker;
}
if (x > 8)
{
return Task.FromResult((8, x - 6));
}
if (x < 2)
{
x = 2;
}
return Task.FromResult((x, 0));
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Tabletop.Core.Constants
{
public enum CoverTypes
{
None = 0,
Light = 1,
Heavy = 2
}
}

View File

@@ -0,0 +1,17 @@
namespace Tabletop.Core.Constants
{
public static class PlayerColors
{
public static readonly List<string> Colors =
[
"#D32F2F", // Rot
"#1976D2", // Blau
"#388E3C", // Grün
"#FBC02D", // Gelb
"#F57C00", // Orange
"#7B1FA2", // Violett
"#00ACC1", // Türkis
"#E91E63" // Magenta
];
}
}

View File

@@ -0,0 +1,30 @@
namespace Tabletop.Core.Constants
{
public static class Roles
{
public const string ADD_USERS = "ADD_USERS";
public const string ADD_UNITS = "ADD_UNITS";
public const string ADD_ABILITIES = "ADD_ABILITIES";
public const string ADD_WEAPONS = "ADD_WEAPONS";
public const string ADD_FRACTIONS = "ADD_FRACTIONS";
public const string ADD_GAMEMODES = "ADD_GAMEMODES";
public const string VIEW_USERS = "VIEW_USERS";
public const string VIEW_UNITS = "VIEW_UNITS";
public const string VIEW_ABILITIES = "VIEW_ABILITIES";
public const string VIEW_WEAPONS = "VIEW_WEAPONS";
public const string VIEW_FRACTIONS = "VIEW_FRACTIONS";
public const string VIEW_GAMEMODES = "VIEW_GAMEMODES";
public const string EDIT_USERS = "EDIT_USERS";
public const string EDIT_UNITS = "EDIT_UNITS";
public const string EDIT_ABILITIES = "EDIT_ABILITIES";
public const string EDIT_WEAPONS = "EDIT_WEAPONS";
public const string EDIT_FRACTIONS = "EDIT_FRACTIONS";
public const string EDIT_GAMEMODES = "EDIT_GAMEMODES";
public const string DELETE_USERS = "DELETE_USERS";
public const string DELETE_UNITS = "DELETE_UNITS";
public const string DELETE_ABILITIES = "DELETE_ABILITIES";
public const string DELETE_WEAPONS = "DELETE_WEAPONS";
public const string DELETE_FRACTIONS = "DELETE_FRACTIONS";
public const string DELETE_GAMEMODES = "DELETE_GAMEMODES";
}
}

774
Tabletop.Core/DeepCopy.cs Normal file
View File

@@ -0,0 +1,774 @@
using System.Linq.Expressions;
using System.Reflection;
#nullable disable
namespace Tabletop.Core
{
/// <summary>
/// Superfast deep copier class, which uses Expression trees.
/// </summary>
public static class DeepCopyByExpressionTrees
{
private static readonly object _isStructTypeToDeepCopyDictionaryLocker = new();
private static Dictionary<Type, bool> _isStructTypeToDeepCopyDictionary = [];
private static readonly object _compiledCopyFunctionsDictionaryLocker = new();
private static Dictionary<Type, Func<object, Dictionary<object, object>, object>> _compiledCopyFunctionsDictionary =
[];
private static readonly Type _objectType = typeof(object);
private static readonly Type _objectDictionaryType = typeof(Dictionary<object, object>);
/// <summary>
/// Creates a deep copy of an object.
/// </summary>
/// <typeparam name="T">Object type.</typeparam>
/// <param name="original">Object to copy.</param>
/// <param name="copiedReferencesDict">Dictionary of already copied objects (Keys: original objects, Values: their copies).</param>
/// <returns></returns>
public static T DeepCopyByExpressionTree<T>(this T original, Dictionary<object, object> copiedReferencesDict = null)
{
return (T)DeepCopyByExpressionTreeObj(original, false, copiedReferencesDict ?? new Dictionary<object, object>(new ReferenceEqualityComparer()));
}
private static object DeepCopyByExpressionTreeObj(object original, bool forceDeepCopy, Dictionary<object, object> copiedReferencesDict)
{
if (original == null)
{
return null;
}
var type = original.GetType();
if (IsDelegate(type))
{
return null;
}
if (!forceDeepCopy && !IsTypeToDeepCopy(type))
{
return original;
}
if (copiedReferencesDict.TryGetValue(original, out object alreadyCopiedObject))
{
return alreadyCopiedObject;
}
if (type == _objectType)
{
return new object();
}
var compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type);
object copy = compiledCopyFunction(original, copiedReferencesDict);
return copy;
}
private static Func<object, Dictionary<object, object>, object> GetOrCreateCompiledLambdaCopyFunction(Type type)
{
// The following structure ensures that multiple threads can use the dictionary
// even while dictionary is locked and being updated by other thread.
// That is why we do not modify the old dictionary instance but
// we replace it with a new instance everytime.
if (!_compiledCopyFunctionsDictionary.TryGetValue(type, out Func<object, Dictionary<object, object>, object> compiledCopyFunction))
{
lock (_compiledCopyFunctionsDictionaryLocker)
{
if (!_compiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction))
{
var uncompiledCopyFunction = CreateCompiledLambdaCopyFunctionForType(type);
compiledCopyFunction = uncompiledCopyFunction.Compile();
var dictionaryCopy = _compiledCopyFunctionsDictionary.ToDictionary(pair => pair.Key, pair => pair.Value);
dictionaryCopy.Add(type, compiledCopyFunction);
_compiledCopyFunctionsDictionary = dictionaryCopy;
}
}
}
return compiledCopyFunction;
}
private static Expression<Func<object, Dictionary<object, object>, object>> CreateCompiledLambdaCopyFunctionForType(Type type)
{
///// INITIALIZATION OF EXPRESSIONS AND VARIABLES
InitializeExpressions(type,
out ParameterExpression inputParameter,
out ParameterExpression inputDictionary,
out ParameterExpression outputVariable,
out ParameterExpression boxingVariable,
out LabelTarget endLabel,
out List<ParameterExpression> variables,
out List<Expression> expressions);
///// RETURN NULL IF ORIGINAL IS NULL
IfNullThenReturnNullExpression(inputParameter, endLabel, expressions);
///// MEMBERWISE CLONE ORIGINAL OBJECT
MemberwiseCloneInputToOutputExpression(type, inputParameter, outputVariable, expressions);
///// STORE COPIED OBJECT TO REFERENCES DICTIONARY
if (IsClassOtherThanString(type))
{
StoreReferencesIntoDictionaryExpression(inputParameter, inputDictionary, outputVariable, expressions);
}
///// COPY ALL NONVALUE OR NONPRIMITIVE FIELDS
FieldsCopyExpressions(type,
inputParameter,
inputDictionary,
outputVariable,
boxingVariable,
expressions);
///// COPY ELEMENTS OF ARRAY
if (IsArray(type) && IsTypeToDeepCopy(type.GetElementType()))
{
CreateArrayCopyLoopExpression(type,
inputParameter,
inputDictionary,
outputVariable,
variables,
expressions);
}
///// COMBINE ALL EXPRESSIONS INTO LAMBDA FUNCTION
var lambda = CombineAllIntoLambdaFunctionExpression(inputParameter, inputDictionary, outputVariable, endLabel, variables, expressions);
return lambda;
}
private static void InitializeExpressions(Type type,
out ParameterExpression inputParameter,
out ParameterExpression inputDictionary,
out ParameterExpression outputVariable,
out ParameterExpression boxingVariable,
out LabelTarget endLabel,
out List<ParameterExpression> variables,
out List<Expression> expressions)
{
inputParameter = Expression.Parameter(_objectType);
inputDictionary = Expression.Parameter(_objectDictionaryType);
outputVariable = Expression.Variable(type);
boxingVariable = Expression.Variable(_objectType);
endLabel = Expression.Label();
variables = [];
expressions = [];
variables.Add(outputVariable);
variables.Add(boxingVariable);
}
private static void IfNullThenReturnNullExpression(ParameterExpression inputParameter, LabelTarget endLabel, List<Expression> expressions)
{
///// Intended code:
/////
///// if (input == null)
///// {
///// return null;
///// }
var ifNullThenReturnNullExpression =
Expression.IfThen(
Expression.Equal(
inputParameter,
Expression.Constant(null, _objectType)),
Expression.Return(endLabel));
expressions.Add(ifNullThenReturnNullExpression);
}
private static void MemberwiseCloneInputToOutputExpression(
Type type,
ParameterExpression inputParameter,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// var output = (<type>)input.MemberwiseClone();
var memberwiseCloneMethod = _objectType.GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
var memberwiseCloneInputExpression =
Expression.Assign(
outputVariable,
Expression.Convert(
Expression.Call(
inputParameter,
memberwiseCloneMethod),
type));
expressions.Add(memberwiseCloneInputExpression);
}
private static void StoreReferencesIntoDictionaryExpression(ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// inputDictionary[(Object)input] = (Object)output;
var storeReferencesExpression =
Expression.Assign(
Expression.Property(
inputDictionary,
_objectDictionaryType.GetProperty("Item"),
inputParameter),
Expression.Convert(outputVariable, _objectType));
expressions.Add(storeReferencesExpression);
}
private static Expression<Func<object, Dictionary<object, object>, object>> CombineAllIntoLambdaFunctionExpression(
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
LabelTarget endLabel,
List<ParameterExpression> variables,
List<Expression> expressions)
{
expressions.Add(Expression.Label(endLabel));
expressions.Add(Expression.Convert(outputVariable, _objectType));
var finalBody = Expression.Block(variables, expressions);
var lambda = Expression.Lambda<Func<object, Dictionary<object, object>, object>>(finalBody, inputParameter, inputDictionary);
return lambda;
}
private static void CreateArrayCopyLoopExpression(Type type,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<ParameterExpression> variables,
List<Expression> expressions)
{
///// Intended code:
/////
///// int i1, i2, ..., in;
/////
///// int length1 = inputarray.GetLength(0);
///// i1 = 0;
///// while (true)
///// {
///// if (i1 >= length1)
///// {
///// goto ENDLABELFORLOOP1;
///// }
///// int length2 = inputarray.GetLength(1);
///// i2 = 0;
///// while (true)
///// {
///// if (i2 >= length2)
///// {
///// goto ENDLABELFORLOOP2;
///// }
///// ...
///// ...
///// ...
///// int lengthn = inputarray.GetLength(n);
///// in = 0;
///// while (true)
///// {
///// if (in >= lengthn)
///// {
///// goto ENDLABELFORLOOPn;
///// }
///// outputarray[i1, i2, ..., in]
///// = (<elementType>)DeepCopyByExpressionTreeObj(
///// (Object)inputarray[i1, i2, ..., in])
///// in++;
///// }
///// ENDLABELFORLOOPn:
///// ...
///// ...
///// ...
///// i2++;
///// }
///// ENDLABELFORLOOP2:
///// i1++;
///// }
///// ENDLABELFORLOOP1:
var rank = type.GetArrayRank();
var indices = GenerateIndices(rank);
variables.AddRange(indices);
var elementType = type.GetElementType();
var assignExpression = ArrayFieldToArrayFieldAssignExpression(inputParameter, inputDictionary, outputVariable, elementType, type, indices);
Expression forExpression = assignExpression;
for (int dimension = 0; dimension < rank; dimension++)
{
var indexVariable = indices[dimension];
forExpression = LoopIntoLoopExpression(inputParameter, indexVariable, forExpression, dimension);
}
expressions.Add(forExpression);
}
private static List<ParameterExpression> GenerateIndices(int arrayRank)
{
///// Intended code:
/////
///// int i1, i2, ..., in;
var indices = new List<ParameterExpression>();
for (int i = 0; i < arrayRank; i++)
{
var indexVariable = Expression.Variable(typeof(int));
indices.Add(indexVariable);
}
return indices;
}
private static BinaryExpression ArrayFieldToArrayFieldAssignExpression(
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
Type elementType,
Type arrayType,
List<ParameterExpression> indices)
{
///// Intended code:
/////
///// outputarray[i1, i2, ..., in]
///// = (<elementType>)DeepCopyByExpressionTreeObj(
///// (Object)inputarray[i1, i2, ..., in]);
var indexTo = Expression.ArrayAccess(outputVariable, indices);
var indexFrom = Expression.ArrayIndex(Expression.Convert(inputParameter, arrayType), indices);
var forceDeepCopy = elementType != _objectType;
var rightSide =
Expression.Convert(
Expression.Call(
_deepCopyByExpressionTreeObjMethod,
Expression.Convert(indexFrom, _objectType),
Expression.Constant(forceDeepCopy, typeof(bool)),
inputDictionary),
elementType);
var assignExpression = Expression.Assign(indexTo, rightSide);
return assignExpression;
}
private static BlockExpression LoopIntoLoopExpression(
ParameterExpression inputParameter,
ParameterExpression indexVariable,
Expression loopToEncapsulate,
int dimension)
{
///// Intended code:
/////
///// int length = inputarray.GetLength(dimension);
///// i = 0;
///// while (true)
///// {
///// if (i >= length)
///// {
///// goto ENDLABELFORLOOP;
///// }
///// loopToEncapsulate;
///// i++;
///// }
///// ENDLABELFORLOOP:
var lengthVariable = Expression.Variable(typeof(int));
var endLabelForThisLoop = Expression.Label();
var newLoop =
Expression.Loop(
Expression.Block(
[],
Expression.IfThen(
Expression.GreaterThanOrEqual(indexVariable, lengthVariable),
Expression.Break(endLabelForThisLoop)),
loopToEncapsulate,
Expression.PostIncrementAssign(indexVariable)),
endLabelForThisLoop);
var lengthAssignment = GetLengthForDimensionExpression(lengthVariable, inputParameter, dimension);
var indexAssignment = Expression.Assign(indexVariable, Expression.Constant(0));
return Expression.Block(
[lengthVariable],
lengthAssignment,
indexAssignment,
newLoop);
}
private static BinaryExpression GetLengthForDimensionExpression(
ParameterExpression lengthVariable,
ParameterExpression inputParameter,
int i)
{
///// Intended code:
/////
///// length = ((Array)input).GetLength(i);
var getLengthMethod = typeof(Array).GetMethod("GetLength", BindingFlags.Public | BindingFlags.Instance);
var dimensionConstant = Expression.Constant(i);
return Expression.Assign(
lengthVariable,
Expression.Call(
Expression.Convert(inputParameter, typeof(Array)),
getLengthMethod,
[dimensionConstant]));
}
private static void FieldsCopyExpressions(Type type,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
ParameterExpression boxingVariable,
List<Expression> expressions)
{
var fields = GetAllRelevantFields(type);
var readonlyFields = fields.Where(f => f.IsInitOnly).ToList();
var writableFields = fields.Where(f => !f.IsInitOnly).ToList();
///// READONLY FIELDS COPY (with boxing)
bool shouldUseBoxing = readonlyFields.Count != 0;
if (shouldUseBoxing)
{
var boxingExpression = Expression.Assign(boxingVariable, Expression.Convert(outputVariable, _objectType));
expressions.Add(boxingExpression);
}
foreach (var field in readonlyFields)
{
if (IsDelegate(field.FieldType))
{
ReadonlyFieldToNullExpression(field, boxingVariable, expressions);
}
else
{
ReadonlyFieldCopyExpression(type,
field,
inputParameter,
inputDictionary,
boxingVariable,
expressions);
}
}
if (shouldUseBoxing)
{
var unboxingExpression = Expression.Assign(outputVariable, Expression.Convert(boxingVariable, type));
expressions.Add(unboxingExpression);
}
///// NOT-READONLY FIELDS COPY
foreach (var field in writableFields)
{
if (IsDelegate(field.FieldType))
{
WritableFieldToNullExpression(field, outputVariable, expressions);
}
else
{
WritableFieldCopyExpression(type,
field,
inputParameter,
inputDictionary,
outputVariable,
expressions);
}
}
}
private static FieldInfo[] GetAllRelevantFields(Type type, bool forceAllFields = false)
{
var fieldsList = new List<FieldInfo>();
var typeCache = type;
while (typeCache != null)
{
fieldsList.AddRange(
typeCache
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
.Where(field => forceAllFields || IsTypeToDeepCopy(field.FieldType)));
typeCache = typeCache.BaseType;
}
return [.. fieldsList];
}
private static FieldInfo[] GetAllFields(Type type)
{
return GetAllRelevantFields(type, forceAllFields: true);
}
private static readonly Type _fieldInfoType = typeof(FieldInfo);
private static readonly MethodInfo _setValueMethod = _fieldInfoType.GetMethod("SetValue", [_objectType, _objectType]);
private static void ReadonlyFieldToNullExpression(FieldInfo field, ParameterExpression boxingVariable, List<Expression> expressions)
{
// This option must be implemented by Reflection because of the following:
// https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f
///// Intended code:
/////
///// fieldInfo.SetValue(boxing, <fieldtype>null);
var fieldToNullExpression =
Expression.Call(
Expression.Constant(field),
_setValueMethod,
boxingVariable,
Expression.Constant(null, field.FieldType));
expressions.Add(fieldToNullExpression);
}
private static readonly Type _thisType = typeof(DeepCopyByExpressionTrees);
private static readonly MethodInfo _deepCopyByExpressionTreeObjMethod = _thisType.GetMethod("DeepCopyByExpressionTreeObj", BindingFlags.NonPublic | BindingFlags.Static);
private static void ReadonlyFieldCopyExpression(Type type,
FieldInfo field,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression boxingVariable,
List<Expression> expressions)
{
// This option must be implemented by Reflection (SetValueMethod) because of the following:
// https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f
///// Intended code:
/////
///// fieldInfo.SetValue(boxing, DeepCopyByExpressionTreeObj((Object)((<type>)input).<field>))
var fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field);
var forceDeepCopy = field.FieldType != _objectType;
var fieldDeepCopyExpression =
Expression.Call(
Expression.Constant(field, _fieldInfoType),
_setValueMethod,
boxingVariable,
Expression.Call(
_deepCopyByExpressionTreeObjMethod,
Expression.Convert(fieldFrom, _objectType),
Expression.Constant(forceDeepCopy, typeof(bool)),
inputDictionary));
expressions.Add(fieldDeepCopyExpression);
}
private static void WritableFieldToNullExpression(FieldInfo field, ParameterExpression outputVariable, List<Expression> expressions)
{
///// Intended code:
/////
///// output.<field> = (<type>)null;
var fieldTo = Expression.Field(outputVariable, field);
var fieldToNullExpression =
Expression.Assign(
fieldTo,
Expression.Constant(null, field.FieldType));
expressions.Add(fieldToNullExpression);
}
private static void WritableFieldCopyExpression(Type type,
FieldInfo field,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// output.<field> = (<fieldType>)DeepCopyByExpressionTreeObj((Object)((<type>)input).<field>);
var fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field);
var fieldType = field.FieldType;
var fieldTo = Expression.Field(outputVariable, field);
var forceDeepCopy = field.FieldType != _objectType;
var fieldDeepCopyExpression =
Expression.Assign(
fieldTo,
Expression.Convert(
Expression.Call(
_deepCopyByExpressionTreeObjMethod,
Expression.Convert(fieldFrom, _objectType),
Expression.Constant(forceDeepCopy, typeof(bool)),
inputDictionary),
fieldType));
expressions.Add(fieldDeepCopyExpression);
}
private static bool IsArray(Type type)
{
return type.IsArray;
}
private static bool IsDelegate(Type type)
{
return typeof(Delegate).IsAssignableFrom(type);
}
private static bool IsTypeToDeepCopy(Type type)
{
return IsClassOtherThanString(type)
|| IsStructWhichNeedsDeepCopy(type);
}
private static bool IsClassOtherThanString(Type type)
{
return !type.IsValueType && type != typeof(string);
}
private static bool IsStructWhichNeedsDeepCopy(Type type)
{
// The following structure ensures that multiple threads can use the dictionary
// even while dictionary is locked and being updated by other thread.
// That is why we do not modify the old dictionary instance but
// we replace it with a new instance everytime.
if (!_isStructTypeToDeepCopyDictionary.TryGetValue(type, out bool isStructTypeToDeepCopy))
{
lock (_isStructTypeToDeepCopyDictionaryLocker)
{
if (!_isStructTypeToDeepCopyDictionary.TryGetValue(type, out isStructTypeToDeepCopy))
{
isStructTypeToDeepCopy = IsStructWhichNeedsDeepCopy_NoDictionaryUsed(type);
var newDictionary = _isStructTypeToDeepCopyDictionary.ToDictionary(pair => pair.Key, pair => pair.Value);
newDictionary[type] = isStructTypeToDeepCopy;
_isStructTypeToDeepCopyDictionary = newDictionary;
}
}
}
return isStructTypeToDeepCopy;
}
private static bool IsStructWhichNeedsDeepCopy_NoDictionaryUsed(Type type)
{
return IsStructOtherThanBasicValueTypes(type)
&& HasInItsHierarchyFieldsWithClasses(type);
}
private static bool IsStructOtherThanBasicValueTypes(Type type)
{
return type.IsValueType
&& !type.IsPrimitive
&& !type.IsEnum
&& type != typeof(decimal);
}
private static bool HasInItsHierarchyFieldsWithClasses(Type type, HashSet<Type> alreadyCheckedTypes = null)
{
alreadyCheckedTypes ??= [];
alreadyCheckedTypes.Add(type);
var allFields = GetAllFields(type);
var allFieldTypes = allFields.Select(f => f.FieldType).Distinct().ToList();
var hasFieldsWithClasses = allFieldTypes.Any(IsClassOtherThanString);
if (hasFieldsWithClasses)
{
return true;
}
var notBasicStructsTypes = allFieldTypes.Where(IsStructOtherThanBasicValueTypes).ToList();
var typesToCheck = notBasicStructsTypes.Where(t => !alreadyCheckedTypes.Contains(t)).ToList();
foreach (var typeToCheck in typesToCheck)
{
if (HasInItsHierarchyFieldsWithClasses(typeToCheck, alreadyCheckedTypes))
{
return true;
}
}
return false;
}
public class ReferenceEqualityComparer : EqualityComparer<object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null)
{
return 0;
}
return obj.GetHashCode();
}
}
}
}

View File

@@ -0,0 +1,25 @@
namespace Tabletop.Core.Extensions
{
public static class StringExtensions
{
/// <summary>
/// Generates a random string
/// </summary>
/// <param name="length">The length of the random string</param>
/// <returns></returns>
public static string RandomString(int length)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[length];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new string(stringChars);
return finalString;
}
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class AbilityFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,8 @@
namespace Tabletop.Core.Filters.Abstract
{
public abstract class FilterBase
{
private string _searchPhrase = string.Empty;
public string SearchPhrase { get => _searchPhrase; set => _searchPhrase = value?.ToUpper() ?? string.Empty; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Tabletop.Core.Filters.Abstract
{
public abstract class PageFilterBase : FilterBase
{
private int _pageNumber = 1;
private int _limit = 30;
public int PageNumber { get => _pageNumber; set => _pageNumber = value < 1 ? 1 : value; }
public int Limit { get => _limit; set => _limit = value < 1 ? 1 : value; }
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class FractionFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class GameFilter : PageFilterBase
{
public int UserId { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class GamemodeFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,11 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class TemplateFilter : PageFilterBase
{
public int UserId { get; set; }
public int FractionId { get; set; }
public int Force { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class UnitFilter : PageFilterBase
{
public int FractionId { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class UserFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class WeaponFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,13 @@
namespace Tabletop.Core.Interfaces
{
/// <summary>
/// Helper interface to provide properties which can be used within the interface <see cref="ILocalizedDbModel{T}"/>
/// </summary>
public interface ILocalizationHelper
{
/// <summary>
/// Gets or sets the ISO 639-1 two letter code for the language
/// </summary>
string Code { get; set; }
}
}

View File

@@ -0,0 +1,45 @@
using DbController.SqlServer;
using DbController;
using Microsoft.AspNetCore.Http;
using Tabletop.Core.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Tabletop.Core.Middlewares
{
public class ConnectionLogMiddleware(RequestDelegate next, IServiceProvider serviceProvider, ILogger<ConnectionLogMiddleware> logger)
{
private readonly RequestDelegate _next = next;
private readonly IServiceProvider _serviceProvider = serviceProvider;
private readonly ILogger<ConnectionLogMiddleware> _logger = logger;
public async Task InvokeAsync(HttpContext context)
{
try
{
// Erstelle ein ServiceScope, um Scoped Services zu verwenden
using var scope = _serviceProvider.CreateScope();
var connectionLogService = scope.ServiceProvider.GetRequiredService<ConnectionLogService>();
// Erstelle das ConnectionLog
var connectionLog = await connectionLogService.GetConnectionLogAsync(context);
if (connectionLog.IpAddress is not ("84.119.112.185" or "127.0.0.1" or "::1"))
{
// Verwende den dbController zur Speicherung des Logs in der DB
using IDbController dbController = new SqlController(AppdataService.ConnectionString);
await ConnectionLogService.CreateAsync(connectionLog, dbController);
}
}
catch (Exception ex)
{
// Fehlerbehandlung und Logging
_logger.LogError($"Fehler beim Erstellen des ConnectionLogs: {ex.Message}", ex);
}
// Weiter zum nächsten Middleware in der Pipeline
await _next(context);
}
}
}

View File

@@ -0,0 +1,64 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Ability : LocalizationModelBase<AbilityDescription>, IDbModel<int?>
{
[CompareField("ability_id")]
public int AbilityId { get; set; }
[CompareField("quality")]
public int Quality { get; set; }
[CompareField("force")]
public int Force { get; set; }
public int? GetIdentifier()
{
return AbilityId > 0 ? AbilityId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "ABILITY_ID", AbilityId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description },
{ "MECHANIC", description.Mechanic }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "ABILITY_ID", AbilityId },
{ "QUALITY", Quality },
{ "FORCE", Force }
};
}
}
public class AbilityDescription : ILocalizationHelper
{
[CompareField("ability_id")]
public int AbilityId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("mechanic")]
public string Mechanic { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,24 @@
using DbController;
namespace Tabletop.Core.Models.Abstract
{
public abstract class ArmyBase
{
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("used_force")]
public int UsedForce { get; set; }
public List<Unit> Units { get; set; } = [];
public int TotalUnits { get; set; }
public virtual Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "FRACTION_ID", FractionId },
{ "USED_FORCE", UsedForce }
};
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Globalization;
using Tabletop.Core.Interfaces;
namespace Tabletop.Core.Models.Abstract
{
public abstract class LocalizationModelBase<T> where T : ILocalizationHelper
{
public List<T> Description { get; set; } = [];
public T? GetLocalization(CultureInfo culture)
{
var description = Description.FirstOrDefault(x => x.Code.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase));
return description;
}
}
}

View File

@@ -0,0 +1,27 @@
using DbController;
namespace Tabletop.Core.Models.Abstract
{
public abstract class ZoneBase
{
[CompareField("x")]
public string X { get; set; } = string.Empty;
[CompareField("y")]
public string Y { get; set; } = string.Empty;
[CompareField("width")]
public string Width { get; set; } = string.Empty;
[CompareField("height")]
public string Height { get; set; } = string.Empty;
public virtual Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "X", X },
{ "Y", Y },
{ "WIDTH", Width },
{ "HEIGHT", Height }
};
}
}
}

View File

@@ -0,0 +1,36 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Capture
{
[CompareField("capture_id")]
public int CaptureId { get; set; }
[CompareField("move_id")]
public int MoveId { get; set; }
[CompareField("player_id")]
public int PlayerId { get; set; }
[CompareField("team_id")]
public int TeamId { get; set; }
[CompareField("capture_zone_nr")]
public int CaptureZoneNr { get; set; }
[CompareField("nr_of_turns")]
public int NrOfTurns { get; set; }
[CompareField("points_received")]
public int PointsReceived { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "CAPTURE_ID", CaptureId },
{ "MOVE_ID", MoveId },
{ "PLAYER_ID", PlayerId },
{ "TEAM_ID", TeamId },
{ "CAPTURE_ZONE_NR", CaptureZoneNr },
{ "NR_OF_TURNS", NrOfTurns },
{ "POINTS_RECEIVED", PointsReceived }
};
}
}
}

View File

@@ -0,0 +1,22 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class CaptureZone : ZoneBase
{
[CompareField("capturezone_id")]
public int CaptureZoneId { get; set; }
[CompareField("points_per_round")]
public int PointsPerRound { get; set; }
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["CAPTUREZONE_ID"] = CaptureZoneId;
baseParams["POINTS_PER_ROUNDS"] = PointsPerRound;
return baseParams;
}
}
}

View File

@@ -0,0 +1,57 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Class : LocalizationModelBase<ClassDescription>
{
[CompareField("class_id")]
public int ClassId { get; set; }
[CompareField("quantity")]
public string Quantity { get; set; } = string.Empty;
public int? GetIdentifier()
{
return ClassId > 0 ? ClassId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "CLASS_ID", ClassId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "CLASS_ID", ClassId },
{ "QUANTITY", Quantity }
};
}
}
public class ClassDescription : ILocalizationHelper
{
[CompareField("class_id")]
public int ClassId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,63 @@
using DbController;
namespace Tabletop.Core.Models
{
public enum DeviceType
{
Desktop,
Tablet,
Smartphone,
Other
}
public enum OperatingSystem
{
Windows,
macOS,
Linux,
iOS,
Android,
Other
}
public class ConnectionLog
{
[CompareField("ip_address")]
public string IpAddress { get; set; } = string.Empty; // IP-Adresse des Besuchers
[CompareField("connection_time")]
public DateTime ConnectionTime { get; set; } // Zeitstempel des Zugriffs
[CompareField("user_agent")]
public string UserAgent { get; set; } = string.Empty; // User-Agent (Browser/OS)
[CompareField("referrer")]
public string Referrer { get; set; } = string.Empty; // Referrer (URL der vorherigen Seite)
[CompareField("requested_url")]
public string RequestedUrl { get; set; } = string.Empty; // Angeforderte URL
[CompareField("geolocation")]
public string? Geolocation { get; set; } = null; // Geografische Lage (optional, kann null sein)
[CompareField("session_id")]
public string SessionId { get; set; } = string.Empty; // Sitzungs-ID
[CompareField("status_code")]
public int StatusCode { get; set; } // HTTP-Statuscode der Antwort
[CompareField("device_type")]
public string DeviceType { get; set; } = string.Empty; // Gerätetyp (Desktop/Tablet/Smartphone)
[CompareField("operating_system")]
public string OperatingSystem { get; set; } = string.Empty; // Betriebssystem des Besuchers
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "IP_ADDRESS", IpAddress },
{ "CONNECTION_TIME", ConnectionTime },
{ "USER_AGENT", UserAgent },
{ "REFERRER", Referrer },
{ "REQUESTED_URL", RequestedUrl },
{ "GEOLOCATION", Geolocation },
{ "SESSION_ID", SessionId },
{ "STATUS_CODE", StatusCode },
{ "DEVICE_TYPE", DeviceType },
{ "OPERATING_SYSTEM", OperatingSystem }
};
}
}
}

View File

@@ -0,0 +1,49 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Elimination
{
[CompareField("elimination_id")]
public int EliminationId { get; set; }
[CompareField("move_id")]
public int MoveId { get; set; }
[CompareField("team_id")]
public int TeamId { get; set; }
[CompareField("atk_id")]
public int AtkId { get; set; }
[CompareField("atk_unit_id")]
public int AtkUnitId { get; set; }
[CompareField("atk_unit_nr")]
public int AtkUnitNr { get; set; }
[CompareField("def_id")]
public int DefId { get; set; }
[CompareField("def_unit_id")]
public int DefUnitId { get; set; }
[CompareField("def_unit_nr")]
public int DefUnitNr { get; set; }
[CompareField("elimination_nr")]
public int EliminationNr { get; set; }
[CompareField("ForcePointsEliminations")]
public int ForcePointsEliminations { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "ELIMINATION_ID", EliminationId },
{ "MOVE_ID", MoveId },
{ "TEAM_ID", TeamId },
{ "ATK_ID", AtkId },
{ "ATK_UNIT_ID", AtkUnitId },
{ "ATK_UNIT_NR", AtkUnitNr },
{ "DEF_ID", DefId },
{ "DEF_UNIT_ID", DefUnitId },
{ "DEF_UNIT_NR", DefUnitNr },
{ "ELIMINATION_NR", EliminationNr },
{ "FORCEPOINTSELIMINATIONS", ForcePointsEliminations }
};
}
}
}

View File

@@ -0,0 +1,64 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Fraction : LocalizationModelBase<FractionDescription>, IDbModel<int?>
{
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public int? GetIdentifier()
{
return FractionId > 0 ? FractionId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", FractionId },
{ "NAME", description.Name },
{ "SHORT_NAME", description.ShortName },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "FRACTION_ID", FractionId },
{ "IMAGE", Image }
};
}
}
public class FractionDescription : ILocalizationHelper
{
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("short_name")]
public string ShortName { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,62 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Game : IDbModel<int?>
{
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("surface_id")]
public int SurfaceId { get; set; }
[CompareField("layout_id")]
public int LayoutId { get; set; }
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("number_of_rounds")]
public int? NumberOfRounds { get; set; }
[CompareField("force")]
public int Force { get; set; }
[CompareField("number_of_teams")]
public int NumberOfTeams { get; set; }
[CompareField("number_of_players")]
public int NumberOfPlayers { get; set; }
[CompareField("date")]
public DateTime Date { get; set; } = DateTime.Now;
public string Host { get; set; } = string.Empty;
public List<Player> Players { get; set; } = [];
public List<Move> Rounds { get; set; } = [];
public List<Capture> Captures { get; set; } = [];
public List<Elimination> Elimination { get; set; } = [];
public string GUID { get; set; } = string.Empty;
public int? GetIdentifier()
{
return GameId > 0 ? GameId : null;
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "GAME_ID", GameId },
{ "GAMEMODE_ID", GamemodeId },
{ "USER_ID", UserId },
{ "SURFACE_ID", SurfaceId },
{ "LAYOUT_ID", LayoutId },
{ "NAME", Name },
{ "NUMBER_OF_ROUNDS", NumberOfRounds },
{ "FORCE", Force },
{ "NUMBER_OF_TEAMS", NumberOfTeams },
{ "NUMBER_OF_PLAYERS", NumberOfPlayers },
{ "DATE", Date }
};
}
}
}

View File

@@ -0,0 +1,63 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Gamemode : LocalizationModelBase<GamemodeDescription>, IDbModel<int?>
{
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public int? GetIdentifier()
{
return GamemodeId > 0 ? GamemodeId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", GamemodeId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description },
{ "MECHANIC", description.Mechanic }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "GAMEMODE_ID", GamemodeId },
{ "IMAGE", Image }
};
}
}
public class GamemodeDescription : ILocalizationHelper
{
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("mechanic")]
public string Mechanic { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace Tabletop.Core.Models
{
public class Geolocation
{
[JsonProperty("country_name")]
public string CountryName { get; set; } = string.Empty;
[JsonProperty("city")]
public string City { get; set; } = string.Empty;
[JsonProperty("latitude")]
public double Latitude { get; set; }
[JsonProperty("longitude")]
public double Longitude { get; set; }
}
}

View File

@@ -0,0 +1,64 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Layout : LocalizationModelBase<LayoutDescription>, IDbModel<int?>
{
[CompareField("layout_id")]
public int LayoutId { get; set; }
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("teams")]
public int Teams { get; set; }
[CompareField("width")]
public string Width { get; set; } = string.Empty;
[CompareField("height")]
public string Height { get; set; } = string.Empty;
public List<SetupZone> SetupZones { get; set; } = [];
public List<CaptureZone> CaptureZones { get; set; } = [];
public int? GetIdentifier()
{
return LayoutId > 0 ? LayoutId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "LAYOUT_ID", LayoutId },
{ "NAME", description.Name }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "LAYOUT_ID", LayoutId },
{ "GAMEMODE_ID", GamemodeId },
{ "TEAMS", Teams },
{ "WIDTH", Width },
{ "HEIGHT", Height }
};
}
}
public class LayoutDescription : ILocalizationHelper
{
[CompareField("layout_id")]
public int LayoutId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,36 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Move
{
[CompareField("move_id")]
public int MoveId { get; set; }
[CompareField("player_id")]
public int PlayerId { get; set; }
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("turn_nr")]
public int TurnNr { get; set; }
[CompareField("move_nr")]
public int MoveNr { get; set; }
[CompareField("start_move")]
public DateTime StartMove { get; set; }
[CompareField("end_move")]
public DateTime EndMove { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "move_id", MoveId },
{ "player_id", PlayerId },
{ "game_id", GameId },
{ "turn_nr", TurnNr },
{ "move_nr", MoveNr },
{ "start_move", StartMove },
{ "end_move", EndMove }
};
}
}
}

View File

@@ -0,0 +1,57 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Permission : LocalizationModelBase<PermissionDescription>
{
[CompareField("permission_id")]
public int PermissionId { get; set; }
[CompareField("identifier")]
public string Identifier { get; set; } = string.Empty;
public int? GetIdentifier()
{
return PermissionId > 0 ? PermissionId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", PermissionId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "PERMISSION_ID", PermissionId },
{ "IDENTIFIER", Identifier }
};
}
}
public class PermissionDescription : ILocalizationHelper
{
[CompareField("permission_id")]
public int PermissionId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,58 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Player : ArmyBase
{
[CompareField("player_id")]
public int PlayerId { get; set; }
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("team")]
public int Team { get; set; }
[CompareField("color")]
public string Color { get; set; } = string.Empty;
[CompareField("points")]
public int Points { get; set; }
[CompareField("order_nr")]
public int OrderNr { get; set; }
[CompareField("start_zone")]
public int StartZone { get; set; }
[CompareField("number_of_eliminations")]
public int NumberOfEliminations { get; set; }
[CompareField("force_points_of_eliminations")]
public int ForcePointsOfEliminations { get; set; }
[CompareField("number_of_remaining_units")]
public int NumberOfRemainingUnits { get; set; }
[CompareField("own_force_points_left")]
public int OwnForcePointsLeft { get; set; }
public int AllowedForce { get; set; }
public User User { get; set; } = new();
public string ConnectionId { get; set; } = string.Empty; // Verbindung zu SignalR
public bool IsReady { get; set; }
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["PLAYER_ID"] = PlayerId;
baseParams["USER_ID"] = UserId;
baseParams["GAME_ID"] = GameId;
baseParams["TEAM"] = Team;
baseParams["COLOR"] = Color;
baseParams["ORDER_NR"] = OrderNr;
baseParams["START_ZONE"] = StartZone;
baseParams["NUMBER_OF_ELIMINATIONS"] = NumberOfEliminations;
baseParams["FORCE_POINTS_OF_ELIMINATIONS"] = ForcePointsOfEliminations;
baseParams["NUMBER_OF_REMAINING_UNITS"] = NumberOfRemainingUnits;
baseParams["OWN_FORCE_POINTS_LEFT"] = OwnForcePointsLeft;
return baseParams;
}
}
}

View File

@@ -0,0 +1,20 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class SetupZone : ZoneBase
{
[CompareField("setupzone_id")]
public int SetupZoneId { get; set; }
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["SETUPZONE_ID"] = SetupZoneId;
return baseParams;
}
}
}

View File

@@ -0,0 +1,59 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Surface : LocalizationModelBase<SurfaceDescription>, IDbModel<int?>
{
[CompareField("surface_id")]
public int SurfaceId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public int? GetIdentifier()
{
return SurfaceId > 0 ? SurfaceId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "SURFACE_ID", SurfaceId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "SURFACE_ID", SurfaceId },
{ "IMAGE", Image }
};
}
}
public class SurfaceDescription : ILocalizationHelper
{
[CompareField("surface_id")]
public int SurfaceId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,27 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Team
{
[CompareField("team_id")]
public int TeamId { get; set; }
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("points")]
public int Points { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "TEAM_ID", TeamId },
{ "GAME_ID", GameId },
{ "NAME", Name },
{ "POINTS", Points}
};
}
}
}

View File

@@ -0,0 +1,34 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Template : ArmyBase, IDbModel<int?>
{
[CompareField("template_id")]
public int TemplateId { get; set; }
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("force")]
public int Force { get; set; }
public int? GetIdentifier()
{
return TemplateId > 0 ? TemplateId : null;
}
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["TEMPLATE_ID"] = TemplateId;
baseParams["USER_ID"] = UserId;
baseParams["NAME"] = Name;
baseParams["FORCE"] = Force;
return baseParams;
}
}
}

View File

@@ -0,0 +1,105 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Unit : LocalizationModelBase<UnitDescription>, IDbModel<int?>
{
[CompareField("unit_id")]
public int UnitId { get; set; }
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("class_id")]
public int ClassId { get; set; }
[CompareField("troop_quantity")]
public int TroopQuantity { get; set; }
[CompareField("defense")]
public int Defense { get; set; }
[CompareField("moving")]
public int Moving { get; set; }
[CompareField("primary_weapon_id")]
public int PrimaryWeaponId { get; set; }
[CompareField("secondary_weapon_id")]
public int? SecondaryWeaponId { get; set; }
[CompareField("first_ability_id")]
public int? FirstAbilityId { get; set; }
[CompareField("second_ability_id")]
public int? SecondAbilityId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public int Force { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public Weapon? PrimaryWeapon { get; set; }
public Weapon? SecondaryWeapon { get; set; }
public Class? Class { get; set; }
public Ability? FirstAbility { get; set; }
public Ability? SecondAbility { get; set; }
[CompareField("quantity")]
public int Quantity { get; set; }
public int ForceOfQuantity { get; set; }
public int? GetIdentifier()
{
return UnitId > 0 ? UnitId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "UNIT_ID", UnitId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description },
{ "MECHANIC", description.Mechanic }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "UNIT_ID", UnitId },
{ "FRACTION_ID", FractionId },
{ "CLASS_ID", ClassId },
{ "TROOP_QUANTITY", TroopQuantity },
{ "DEFENSE", Defense },
{ "MOVING", Moving },
{ "PRIMARY_WEAPON_ID", PrimaryWeaponId },
{ "SECONDARY_WEAPON_ID", SecondaryWeaponId },
{ "FIRST_ABILITY_ID", FirstAbilityId },
{ "SECOND_ABILITY_ID" , SecondAbilityId },
{ "QUANTITY", Quantity }
};
}
}
public class UnitDescription : ILocalizationHelper
{
[CompareField("unit_id")]
public int UnitId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("mechanic")]
public string Mechanic { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,56 @@
using DbController;
namespace Tabletop.Core.Models
{
public sealed class User : IDbModel<int?>
{
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("username")]
public string Username { get; set; } = string.Empty;
[CompareField("display_name")]
public string DisplayName { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("main_fraction_id")]
public int MainFractionId { get; set; }
[CompareField("password")]
public string Password { get; set; } = string.Empty;
[CompareField("salt")]
public string Salt { get; set; } = string.Empty;
[CompareField("last_login")]
public DateTime LastLogin { get; set; }
[CompareField("registration_date")]
public DateTime RegistrationDate { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public List<Permission> Permissions { get; set; } = [];
public List<Unit> Units { get; set; } = [];
public int? GetIdentifier()
{
return UserId > 0 ? UserId : null;
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "USER_ID", UserId },
{ "USERNAME", Username },
{ "DISPLAY_NAME", DisplayName },
{ "DESCRIPTION", Description },
{ "MAIN_FRACTION_ID", MainFractionId },
{ "PASSWORD", Password },
{ "SALT", Salt },
{ "LAST_LOGIN", LastLogin },
{ "REGISTRATION_DATE", RegistrationDate },
{ "IMAGE", Image }
};
}
}
}

View File

@@ -0,0 +1,12 @@
using DbController;
namespace Tabletop.Core.Models
{
public sealed class UserPermission
{
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("permission_id")]
public int PermissionId { get; set; }
}
}

View File

@@ -0,0 +1,69 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Weapon : LocalizationModelBase<WeaponDescription>, IDbModel<int?>
{
[CompareField("weapon_id")]
public int WeaponId { get; set; }
[CompareField("attack")]
public int Attack { get; set; }
[CompareField("quality")]
public int Quality { get; set; }
[CompareField("range")]
public int Range { get; set; }
[CompareField("dices")]
public int Dices { get; set; }
public int? GetIdentifier()
{
return WeaponId > 0 ? WeaponId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", WeaponId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "WEAPON_ID", WeaponId },
{ "ATTACK", Attack },
{ "QUALITY", Quality },
{ "RANGE", Range },
{ "DICES", Dices }
};
}
}
public class WeaponDescription : ILocalizationHelper
{
[CompareField("weapon_id")]
public int WeaponId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,209 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class AbilityService : IModelService<Ability, int, AbilityFilter>
{
public async Task CreateAsync(Ability input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Fractions
(
Quality,
Force
)
VALUES
(
@QUALITY,
@FORCE
); {dbController.GetLastIdSql()}";
input.AbilityId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO AbilityDescription
(
AbilityId,
Code,
Name,
Description,
Mechanic
)
VALUES
(
@ABILITY_ID,
@CODE,
@NAME,
@DESCRIPTION,
@MECHANIC
)";
var parameters = new
{
ABILTIY_ID = input.AbilityId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Ability input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Abilities WHERE AbilityId = @ABILITY_ID";
await dbController.QueryAsync(sql, new
{
ABILITY_ID = input.AbilityId
}, cancellationToken);
}
public static async Task<List<Ability>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Abilities";
var list = await dbController.SelectDataAsync<Ability>(sql, cancellationToken: cancellationToken);
await LoadClassDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<Ability?> GetAsync(int abilityId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Abilities WHERE AbilityId = @ABILITY_ID";
var ability = await dbController.GetFirstAsync<Ability>(sql, new
{
ABILITY_ID = abilityId
}, cancellationToken);
return ability;
}
private static async Task LoadClassDescriptionsAsync(List<Ability> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> abilityIds = list.Select(x => x.AbilityId);
string sql = $"SELECT * FROM AbilityDescription WHERE AbilityId IN ({string.Join(",", abilityIds)})";
List<AbilityDescription> descriptions = await dbController.SelectDataAsync<AbilityDescription>(sql, null, cancellationToken);
foreach (var item in list)
{
item.Description = [.. descriptions.Where(x => x.AbilityId == item.AbilityId)];
}
}
}
public async Task UpdateAsync(Ability input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Abilities SET
Quality = @QUALITY,
Force = @FORCE
WHERE AbilityId = @ABILITY_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE AbilityDescription SET
Name = @NAME,
Description = @DESCRIPTION,
Mechanic = @MECHANIC
WHERE AbilityId = @ABILITY_ID AND Code = @CODE";
var parameters = new
{
ABILITY_ID = input.AbilityId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description,
MECHANIC = description.Mechanic
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task<List<Ability>> GetAsync(AbilityFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT ad.Name, a.* " +
"FROM AbilityDescription ad " +
"INNER JOIN Abilities a " +
"ON (a.AbilityId = ad.AbilityId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<Ability> list = await dbController.SelectDataAsync<Ability>(sql, GetFilterParameter(filter), cancellationToken);
await LoadAbilityDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<int> GetTotalAsync(AbilityFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM AbilityDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public string GetFilterWhere(AbilityFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public Dictionary<string, object?> GetFilterParameter(AbilityFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
private static async Task LoadAbilityDescriptionsAsync(List<Ability> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> abilityIds = list.Select(x => x.AbilityId);
string sql = $"SELECT * FROM AbilityDescription WHERE AbilityId IN ({string.Join(",", abilityIds)})";
List<AbilityDescription> descriptions = await dbController.SelectDataAsync<AbilityDescription>(sql, null, cancellationToken);
foreach (var ability in list)
{
ability.Description = [.. descriptions.Where(x => x.AbilityId == ability.AbilityId)];
}
}
}
}
}

View File

@@ -0,0 +1,128 @@
using DbController;
using DbController.SqlServer;
using Microsoft.Extensions.Configuration;
using System.Globalization;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public static class AppdataService
{
public static string[] SupportedCultureCodes => [.. SupportedCultures.Select(x => x.Name)];
public static CultureInfo[] SupportedCultures =>
[
new ("en"),
new ("de")
];
public static bool FirstUserExists { get; set; } = false;
public static List<Permission> Permissions { get; set; } = [];
public static List<Unit> Units { get; set; } = [];
public static List<Weapon> Weapons { get; set; } = [];
public static List<Fraction> Fractions { get; set; } = [];
public static List<Gamemode> Gamemodes { get; set; } = [];
public static List<Class> Classes { get; set; } = [];
public static List<Ability> Abilities { get; set; } = [];
public static List<Layout> Layouts { get; set; } = [];
public static List<Surface> Surfaces { get; set; } = [];
public static readonly Dictionary<string, Game> Games = [];
private static IConfiguration? _configuration;
public static async Task InitAsync(IConfiguration configuration)
{
_configuration = configuration;
using IDbController dbController = new SqlController(ConnectionString);
Permissions = await PermissionService.GetAllAsync(dbController);
FirstUserExists = await UserService.FirstUserExistsAsync(dbController);
Units = await UnitService.GetAllAsync(dbController);
Weapons = await WeaponService.GetAllAsync(dbController);
Fractions = await FractionService.GetAllAsync(dbController);
Gamemodes = await GamemodeService.GetAllAsync(dbController);
Classes = await ClassService.GetAllAsync(dbController);
Abilities = await AbilityService.GetAllAsync(dbController);
Layouts = await LayoutService.GetAllAsync(dbController);
Surfaces = await SurfaceService.GetAllAsync(dbController);
}
/// <summary>
/// Creates or updates an object in the corresponding list fro the type <see cref="T"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
public static void UpdateRecord<T>(T input) where T : class, IDbModel<int?>, new()
{
List<T> list = GetList<T>();
T? item = list.FirstOrDefault(x => x.GetIdentifier() == input.GetIdentifier());
if (item is null)
{
list.Add(input);
}
else
{
int index = list.IndexOf(item);
list[index] = input;
}
}
/// <summary>
/// Deletes an item from the corresponding object list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
public static void DeleteRecord<T>(T input) where T : class, IDbModel<int?>, new()
{
List<T> list = GetList<T>();
list.Remove(input);
}
/// <summary>
/// Gets the corresponding list for the type <see cref="T"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>This method never returns null. When no list for <see cref="T"/> is specified, it returns a new empty list</returns>
public static List<T> GetList<T>() where T : class, IDbModel<int?>, new()
{
var input = new T();
object tmp = input switch
{
Permission => Permissions,
_ => new List<T>()
};
List<T> list = (List<T>)tmp;
return list;
}
public static string ConnectionString => _configuration?["Sql:ConnectionString"] ?? string.Empty;
public static int PageLimit => _configuration?.GetValue<int>("Filter:PageLimit") ?? 30;
public static string ApiKey => _configuration?["IpGeolocation:ApiKey"] ?? string.Empty;
public static CultureInfo ToCulture(this ILocalizationHelper helper)
{
var culture = SupportedCultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals(helper.Code, StringComparison.OrdinalIgnoreCase));
if (culture is null)
{
return SupportedCultures[0];
}
else
{
return culture;
}
}
}
}

View File

@@ -0,0 +1,62 @@
using DbController;
using DbController.SqlServer;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class AuthService(AuthenticationStateProvider authenticationStateProvider, UserService userService)
{
/// <summary>
/// Converts the active claims into a <see cref="User"/> object
/// </summary>
/// <returns></returns>
public async Task<User?> GetUserAsync(IDbController? dbController = null)
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity is not null && user.Identity.IsAuthenticated)
{
Claim? claim = user.FindFirst("UserId");
if (claim is null)
{
return null;
}
var userId = Convert.ToInt32(claim.Value);
bool shouldDispose = dbController is null;
dbController ??= new SqlController(AppdataService.ConnectionString);
var result = await userService.GetAsync(userId, dbController);
if (shouldDispose)
{
dbController.Dispose();
}
return result;
}
return null;
}
/// <summary>
/// Checks if the currently logged in user as a specific role within it's claims.
/// </summary>
/// <param name="roleName"></param>
/// <returns></returns>
public async Task<bool> HasRole(string roleName)
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
return user.IsInRole(roleName);
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tabletop.Core.Services
{
internal class CaptureService
{
}
}

View File

@@ -0,0 +1,53 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class ClassService : IModelService<Class, int>
{
public Task CreateAsync(Class input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task DeleteAsync(Class input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public static async Task<List<Class>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Classes";
var list = await dbController.SelectDataAsync<Class>(sql, cancellationToken: cancellationToken);
await LoadClassDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public Task<Class?> GetAsync(int identifier, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
private static async Task LoadClassDescriptionsAsync(List<Class> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> classIds = list.Select(x => x.ClassId);
string sql = $"SELECT * FROM ClassDescription WHERE ClassId IN ({string.Join(",", classIds)})";
List<ClassDescription> descriptions = await dbController.SelectDataAsync<ClassDescription>(sql, null, cancellationToken);
foreach (var item in list)
{
item.Description = [.. descriptions.Where(x => x.ClassId == item.ClassId)];
}
}
}
public Task UpdateAsync(Class input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,132 @@
using DbController;
using Microsoft.AspNetCore.Http;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class ConnectionLogService(GeolocationService geolocationService)
{
private readonly GeolocationService _geolocationService = geolocationService;
public static async Task CreateAsync(ConnectionLog input, IDbController dbController, CancellationToken cancellationToken = default)
{
// SQL-Insert-Statement für das ConnectionLog
string sql = $@"
INSERT INTO ConnectionLogs
(
IpAddress,
ConnectionTime,
UserAgent,
Referrer,
RequestedUrl,
Geolocation,
SessionId,
StatusCode,
DeviceType,
OperatingSystem
)
VALUES
(
@IP_ADDRESS,
@CONNECTION_TIME,
@USER_AGENT,
@REFERRER,
@REQUESTED_URL,
@GEOLOCATION,
@SESSION_ID,
@STATUS_CODE,
@DEVICE_TYPE,
@OPERATING_SYSTEM
); {dbController.GetLastIdSql()}";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
public async Task<ConnectionLog> GetConnectionLogAsync(HttpContext context)
{
// Extrahiere die IP-Adresse
var ipAddress = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
// Hole die Geolocation (Stadt, Land, Koordinaten etc.) basierend auf der IP-Adresse
var geolocation = await _geolocationService.GetGeolocationAsync(ipAddress);
// Extrahiere den User-Agent-Header aus dem HttpContext
var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault() ?? "Unknown";
// Bestimmung des DeviceTypes und des Betriebssystems
var deviceType = await GetDeviceType(userAgent);
var operatingSystem = await GetOperatingSystem(userAgent);
// Erstelle das ConnectionLog
return new ConnectionLog
{
IpAddress = ipAddress,
ConnectionTime = DateTime.UtcNow,
UserAgent = userAgent,
Referrer = context.Request.Headers["Referer"].FirstOrDefault() ?? "Unknown",
RequestedUrl = context.Request.Path,
Geolocation = geolocation, // Füge die Geolocation hinzu
SessionId = context.Session?.Id ?? Guid.NewGuid().ToString(),
StatusCode = context.Response.StatusCode,
DeviceType = deviceType,
OperatingSystem = operatingSystem
};
}
// Methode zur Bestimmung des Device-Typs anhand des User-Agent-Strings
public static Task<string> GetDeviceType(string userAgent)
{
string deviceType;
if (userAgent.Contains("Mobi") || userAgent.Contains("Android") || userAgent.Contains("iPhone") || userAgent.Contains("iPad"))
{
deviceType = "Mobile";
}
else if (userAgent.Contains("Tablet") || userAgent.Contains("iPad"))
{
deviceType = "Tablet";
}
else
{
deviceType = "Desktop"; // Default ist Desktop
}
return Task.FromResult(deviceType); // Verpackt den Wert in einem Task
}
// Methode zur Bestimmung des Betriebssystems anhand des User-Agent-Strings
public static Task<string> GetOperatingSystem(string userAgent)
{
string operatingSystem;
if (userAgent.Contains("Windows"))
{
operatingSystem = "Windows";
}
else if (userAgent.Contains("Mac OS"))
{
operatingSystem = "macOS";
}
else if (userAgent.Contains("Linux"))
{
operatingSystem = "Linux";
}
else if (userAgent.Contains("Android"))
{
operatingSystem = "Android";
}
else if (userAgent.Contains("iPhone") || userAgent.Contains("iPad"))
{
operatingSystem = "iOS";
}
else
{
operatingSystem = "Other"; // Default: Unbekanntes Betriebssystem
}
return Task.FromResult(operatingSystem); // Verpackt den Wert in einem Task
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tabletop.Core.Services
{
internal class EliminationService
{
}
}

View File

@@ -0,0 +1,189 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class FractionService : IModelService<Fraction, int, FractionFilter>
{
public async Task CreateAsync(Fraction input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Fractions
(
)
VALUES
(
); {dbController.GetLastIdSql()}";
input.FractionId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO FractionDescription
(
FractionId,
Code,
Name,
ShortName,
Description
)
VALUES
(
@FRACTION_ID,
@CODE,
@NAME,
@SHORT_NAME,
@DESCRIPTION
)";
var parameters = new
{
FRACTION_ID = input.FractionId,
CODE = description.Code,
NAME = description.Name,
SHORT_NAME = description.ShortName,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Fraction input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Fractions WHERE FractionId = @FRACTION_ID";
await dbController.QueryAsync(sql, new
{
FRACTION_ID = input.FractionId
}, cancellationToken);
}
public async Task<Fraction?> GetAsync(int fractionId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Fractions WHERE FractionId = @FRACTION_ID";
var fraction = await dbController.GetFirstAsync<Fraction>(sql, new
{
FRACTION_ID = fractionId
}, cancellationToken);
return fraction;
}
public static async Task<List<Fraction>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Fractions";
var list = await dbController.SelectDataAsync<Fraction>(sql, cancellationToken: cancellationToken);
await LoadFractionDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task UpdateAsync(Fraction input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Fractions SET
Image = @IMAGE
WHERE FractionId = @FRACTION_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE FractionDescription SET
Name = @NAME,
Description = @DESCRIPTION,
ShortName = @SHORT_NAME
WHERE FractionId = @FRACTION_ID AND Code = @CODE";
var parameters = new
{
FRACTION_ID = input.FractionId,
CODE = description.Code,
NAME = description.Name,
SHORT_NAME = description.ShortName,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task<List<Fraction>> GetAsync(FractionFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT fd.Name, f.* " +
"FROM FractionDescription fd " +
"INNER JOIN Fractions f " +
"ON (f.FractionId = fd.FractionId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<Fraction> list = await dbController.SelectDataAsync<Fraction>(sql, GetFilterParameter(filter), cancellationToken);
await LoadFractionDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<int> GetTotalAsync(FractionFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM FractionDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public string GetFilterWhere(FractionFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public Dictionary<string, object?> GetFilterParameter(FractionFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
private static async Task LoadFractionDescriptionsAsync(List<Fraction> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> fractionIds = list.Select(x => x.FractionId);
string sql = $"SELECT * FROM FractionDescription WHERE FractionId IN ({string.Join(",", fractionIds)})";
List<FractionDescription> descriptions = await dbController.SelectDataAsync<FractionDescription>(sql, null, cancellationToken);
foreach (var fraction in list)
{
fraction.Description = [.. descriptions.Where(x => x.FractionId == fraction.FractionId)];
}
}
}
}
}

View File

@@ -0,0 +1,182 @@
using DbController;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class GameService(PlayerService playerService, UserService userService) : IModelService<Game, int, GameFilter>
{
private readonly PlayerService _playerService = playerService;
private readonly UserService _userService = userService;
public async Task CreateAsync(Game input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Games
(
UserId,
GamemodeId,
SurfaceId,
LayoutId,
Name,
NumberOfRounds,
Force,
NumberOfTeams,
NumberOfPlayers,
Date
)
VALUES
(
@USER_ID,
@GAMEMODE_ID,
@SURFACE_ID,
@LAYOUT_ID,
@NAME,
@NUMBER_OF_ROUNDS,
@FORCE,
@NUMBER_OF_TEAMS,
@NUMBER_OF_PLAYERS,
@DATE
); {dbController.GetLastIdSql()}";
input.GameId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var item in input.Players)
{
item.GameId = input.GameId;
await _playerService.CreateAsync(item, dbController, cancellationToken);
}
}
public async Task DeleteAsync(Game input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Games WHERE GameId = @GAME_ID";
await dbController.QueryAsync(sql, new
{
GAME_ID = input.GameId,
}, cancellationToken);
}
public async Task<Game?> GetAsync(int gameId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Games WHERE GameId = @GAME_ID";
var game = await dbController.GetFirstAsync<Game>(sql, new
{
GAME_ID = gameId
}, cancellationToken);
if (game != null)
{
game.Players = await _playerService.GetGamePlayersAsync(game.GameId, dbController, cancellationToken);
}
return game;
}
public static async Task<List<Game>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT * FROM Games";
var list = await dbController.SelectDataAsync<Game>(sql, cancellationToken: cancellationToken);
return list;
}
public async Task<List<Game>> GetAsync(GameFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT DISTINCT g.* FROM Games g LEFT JOIN Players p ON g.GameId = p.GameId WHERE p.UserId = @USER_ID OR g.UserId = @USER_ID ");
sb.AppendLine(GetFilterWhere(filter));
sb.AppendLine(@$" ORDER BY Date DESC ");
sb.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sb.ToString();
List<Game> list = await dbController.SelectDataAsync<Game>(sql, GetFilterParameter(filter), cancellationToken);
if (list.Count != 0)
{
foreach (var item in list)
{
item.Players = await _playerService.GetGamePlayersAsync(item.GameId, dbController, cancellationToken);
item.Host = await _userService.GetUsernameAsync(item.UserId, dbController, cancellationToken);
}
}
return list;
}
public async Task<List<Game>> GetLastGamesOfPlayerAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT TOP 5 g.GameId, g.GamemodeId, g.Date, g.Force FROM Games g INNER JOIN Players p ON g.GameId = p.GameId WHERE p.UserId = @USER_ID ORDER BY g.Date DESC";
var list = await dbController.SelectDataAsync<Game>(sql, new
{
USER_ID = userId,
}, cancellationToken: cancellationToken);
return list;
}
public Dictionary<string, object?> GetFilterParameter(GameFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "USER_ID", filter.UserId }
};
}
public string GetFilterWhere(GameFilter filter)
{
StringBuilder sb = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sb.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sb.ToString();
return sql;
}
public async Task<int> GetTotalAsync(GameFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT COUNT(*) FROM Games WHERE UserId = @USER_ID");
sb.AppendLine(GetFilterWhere(filter));
string sql = sb.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public async Task UpdateAsync(Game input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Games SET
GamemodeId = @GAMEMODE_ID,
SurfaceId = @SURFACE_ID,
LayoutId = @LAYOUT_ID,
Name = @NAME,
NumberOfRounds = @NUMBER_OF_ROUNDS,
Force = @FORCE,
NumberOfTeams = @NUMBER_OF_TEAMS,
NumberOfPlayers = @NUMBER_OF_PLAYERS,
Date = @DATE
WHERE GameId = @GAME_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
}
}

View File

@@ -0,0 +1,189 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class GamemodeService : IModelService<Gamemode, int, GamemodeFilter>
{
public async Task CreateAsync(Gamemode input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Gamemodes
(
)
VALUES
(
); {dbController.GetLastIdSql()}";
input.GamemodeId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO GamemodeDescription
(
GamemodeId,
Code,
Name,
Description,
Mechanic
)
VALUES
(
@GAMEMODE_ID,
@CODE,
@NAME,
@DESCRIPTION,
@MECHANIC
)";
var parameters = new
{
GAMEMODE_ID = input.GamemodeId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Gamemode input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Gamemodes WHERE GamemodeId = @GAMEMODE_ID";
await dbController.QueryAsync(sql, new
{
GAMEMODE_ID = input.GamemodeId
}, cancellationToken);
}
public async Task<Gamemode?> GetAsync(int gamemodeId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Gamemodes WHERE GamemodeId = @GAMEMODE_ID";
var gamemode = await dbController.GetFirstAsync<Gamemode>(sql, new
{
GAMEMODE_ID = gamemodeId
}, cancellationToken);
return gamemode;
}
public static async Task<List<Gamemode>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Gamemodes";
var list = await dbController.SelectDataAsync<Gamemode>(sql, cancellationToken: cancellationToken);
await LoadGamemodeDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task UpdateAsync(Gamemode input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql;
//string sql = @"UPDATE Gamemodes SET
// Image = @IMAGE
// WHERE GamemodeId = @GAMEMODE_ID";
//await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE GamemodeDescription SET
Name = @NAME,
Description = @DESCRIPTION,
Mechanic = @MECHANIC
WHERE GamemodeId = @GAMEMODE_ID AND Code = @CODE";
var parameters = new
{
GAMEMODE_ID = input.GamemodeId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description,
MECHANIC = description.Mechanic
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task<List<Gamemode>> GetAsync(GamemodeFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT gd.Name, g.* " +
"FROM GamemodeDescription gd " +
"INNER JOIN Gamemodes g " +
"ON (g.GamemodeId = gd.GamemodeId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<Gamemode> list = await dbController.SelectDataAsync<Gamemode>(sql, GetFilterParameter(filter), cancellationToken);
await LoadGamemodeDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<int> GetTotalAsync(GamemodeFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM GamemodeDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public string GetFilterWhere(GamemodeFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public Dictionary<string, object?> GetFilterParameter(GamemodeFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
private static async Task LoadGamemodeDescriptionsAsync(List<Gamemode> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> gamemodeIds = list.Select(x => x.GamemodeId);
string sql = $"SELECT * FROM GamemodeDescription WHERE GamemodeId IN ({string.Join(",", gamemodeIds)})";
List<GamemodeDescription> descriptions = await dbController.SelectDataAsync<GamemodeDescription>(sql, null, cancellationToken);
foreach (var gamemode in list)
{
gamemode.Description = [.. descriptions.Where(x => x.GamemodeId == gamemode.GamemodeId)];
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
namespace Tabletop.Core.Services
{
public class GeolocationService(HttpClient httpClient)
{
private readonly HttpClient _httpClient = httpClient;
public Task<string> GetGeolocationAsync(string ipAddress)
{
//// Überprüfen, ob die IP-Adresse lokal ist (127.0.0.1 oder localhost)
//if (await IsLocalIpAddressAsync(ipAddress))
//{
// // Wenn es eine lokale IP-Adresse ist, keine Geolokalisierung durchführen
// return "Lokale IP-Adresse";
//}
//try
//{
// // URL für Geolokalisierung
// var url = $"https://api.ipgeolocation.io/ipgeo?apiKey={AppdataService.ApiKey}&ip={ipAddress}";
// // HTTP-Anfrage senden
// var response = await _httpClient.GetAsync(url);
// // Fehlerbehandlung: Prüfen, ob der Statuscode erfolgreich war
// response.EnsureSuccessStatusCode(); // Wird eine Ausnahme werfen, wenn der Statuscode nicht erfolgreich ist
// // Rückgabe der Geolokalisierungsdaten
// return await response.Content.ReadAsStringAsync();
//}
//catch (HttpRequestException ex)
//{
// // Fehlerbehandlung: Loggen oder Weiterverarbeiten des Fehlers
// throw new Exception($"Fehler bei der Geolokalisierung: {ex.Message}", ex);
//}
return Task.FromResult("Lokale IP-Adresse");
}
// Hilfsmethode, um zu prüfen, ob die IP-Adresse lokal ist
private static Task<bool> IsLocalIpAddressAsync(string ipAddress)
{
// Überprüfen, ob die IP-Adresse eine der folgenden ist
bool isLocal = ipAddress == "127.0.0.1" || ipAddress == "localhost" ||
ipAddress.StartsWith("10.") || ipAddress.StartsWith("192.168.") ||
ipAddress.StartsWith("172.16.");
return Task.FromResult(isLocal); // Rückgabe des Ergebnisses als Task
}
}
}

View File

@@ -0,0 +1,97 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class LayoutService : IModelService<Layout, int>
{
public async Task CreateAsync(Layout input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Layouts
(
GamemodeId,
Teams,
Width,
Height
)
VALUES
(
@GAMEMODE_ID
@TEAMS,
@WIDTH,
@HEIGHT
); {dbController.GetLastIdSql()}";
input.LayoutId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
}
public async Task DeleteAsync(Layout input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Layouts WHERE LayoutId = @LAYOUT_ID";
await dbController.QueryAsync(sql, new
{
LAYOUT_ID = input.LayoutId
}, cancellationToken);
}
public static async Task<List<SetupZone>> GetSetupZonesAsync(int layoutId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT sz.SetupZoneId, sz.LayoutId, z.*
FROM SetupZones sz
INNER JOIN Zones z ON (z.ZoneId = sz.ZoneId)
WHERE sz.LayoutId = @LAYOUT_ID";
var list = await dbController.SelectDataAsync<SetupZone>(sql, new
{
LAYOUT_ID = layoutId
}, cancellationToken);
return list;
}
public static async Task<List<CaptureZone>> GetCaptureZonesAsync(int layoutId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT cz.CaptureZoneId, cz.LayoutId, z.*
FROM CaptureZones cz
INNER JOIN Zones z ON (z.ZoneId = cz.ZoneId)
WHERE cz.LayoutId = @LAYOUT_ID";
var list = await dbController.SelectDataAsync<CaptureZone>(sql, new
{
LAYOUT_ID = layoutId
}, cancellationToken);
return list;
}
public static async Task<List<Layout>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Layouts";
var list = await dbController.SelectDataAsync<Layout>(sql, cancellationToken: cancellationToken);
foreach(var layout in list)
{
layout.SetupZones = await GetSetupZonesAsync(layout.LayoutId, dbController, cancellationToken);
layout.CaptureZones = await GetCaptureZonesAsync(layout.LayoutId, dbController, cancellationToken);
}
return list;
}
public Task<Layout?> GetAsync(int battlegroundId, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task UpdateAsync(Layout input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,57 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class MoveService : IModelService<Move, int>
{
public async Task CreateAsync(Move input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Moves
(
PlayerId,
GameId,
TurnNr,
MoveNr,
StartMove,
EndMove
)
VALUES
(
@PLAYER_ID
@GAME_ID,
@TURN_NR,
@MOVE_NR,
@START_MOVE,
@END_END
); {dbController.GetLastIdSql()}";
input.MoveId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
}
public Task DeleteAsync(Move input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task<Move?> GetAsync(int identifier, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public async Task UpdateAsync(Move input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Moves SET
PlayerId = @PLAYER_ID,
GameId = @GAME_ID,
TurnNr = @TURN_NR,
MoveNr = @MOVE_NR,
StartMove = @START_MOVE,
EndMove = @END_END
WHERE MoveId = @MOVE_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
}
}

View File

@@ -0,0 +1,84 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class PermissionService
{
public static async Task<List<Permission>> GetUserPermissionsAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT p.*
FROM UserPermissions up
INNER JOIN Permissions p ON (p.PermissionId = up.PermissionId)
WHERE UserId = @USER_ID";
var list = await dbController.SelectDataAsync<Permission>(sql, new
{
USER_ID = userId
}, cancellationToken);
await LoadPermissionDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task UpdateUserPermissionsAsync(User user, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Step 1: Delete all permissions for the user.
string sql = "DELETE FROM UserPermissions WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId
}, cancellationToken);
// Step 2: Add all permissions from the object back.
foreach (var permission in user.Permissions)
{
sql = @"INSERT INTO UserPermissions
(
UserId,
PermissionId
)
VALUES
(
@USER_ID,
@PERMISSION_ID
)";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId,
PERMISSION_ID = permission.PermissionId
}, cancellationToken);
}
}
public static async Task<List<Permission>> GetAllAsync(IDbController dbController)
{
string sql = "SELECT * FROM Permissions";
var list = await dbController.SelectDataAsync<Permission>(sql);
await LoadPermissionDescriptionsAsync(list, dbController);
return list;
}
private static async Task LoadPermissionDescriptionsAsync(List<Permission> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> permissionIds = list.Select(x => x.PermissionId);
string sql = $"SELECT * FROM PermissionDescription WHERE PermissionId IN ({string.Join(",", permissionIds)})";
List<PermissionDescription> descriptions = await dbController.SelectDataAsync<PermissionDescription>(sql, null, cancellationToken);
foreach (var permission in list)
{
permission.Description = [.. descriptions.Where(x => x.PermissionId == permission.PermissionId)];
}
}
}
}
}

View File

@@ -0,0 +1,127 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class PlayerService(UserService userService, UnitService unitService) : IModelService<Player, int>
{
private readonly UserService _userService = userService;
private readonly UnitService _unitService = unitService;
public async Task CreateAsync(Player input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Players
(
UserId,
GameId,
FractionId,
Team,
Color,
UsedForce
)
VALUES
(
@USER_ID,
@GAME_ID,
@FRACTION_ID,
@TEAM,
@COLOR,
@USED_FORCE
); {dbController.GetLastIdSql()}";
input.PlayerId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
}
public async Task DeleteAsync(Player Input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Players WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, new
{
Input_ID = Input.PlayerId
}, cancellationToken);
}
public static async Task DeleteByGameAsync(int gameId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Players WHERE GameId = @GAME_ID";
await dbController.QueryAsync(sql, new
{
GAME_ID = gameId
}, cancellationToken);
}
public Task<Player?> GetAsync(int identifier, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public async Task<List<Player>> GetGamePlayersAsync(int gameId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = ("SELECT * FROM Players WHERE GameId = @GAME_ID");
List<Player> list = await dbController.SelectDataAsync<Player>(sql, new
{
GAME_ID = gameId
}, cancellationToken);
if (list.Count != 0)
{
foreach (var item in list)
{
item.Units = await _unitService.GetPlayerUnitsAsync(item.PlayerId, dbController, cancellationToken);
item.User = await _userService.GetUserInformationAsync(item.UserId, dbController, cancellationToken) ?? new();
}
}
return list;
}
public async Task UpdateAsync(Player player, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeletePlayerUnitsAsync(player.PlayerId, dbController, cancellationToken);
string sql = @"UPDATE Players SET
Points = @POINTS
WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, player.GetParameters(), cancellationToken);
}
public async Task UpdatePlayerOrderAsync(Player player, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Players SET
OrderNr = @ORDER_NR,
StartZone = @START_ZONE
WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, player.GetParameters(), cancellationToken);
}
public async Task UpdatePlayerArmyAsync(Player player, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeletePlayerUnitsAsync(player.PlayerId, dbController, cancellationToken);
string sql = @"UPDATE Players SET
UsedForce = @USED_FORCE
WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, player.GetParameters(), cancellationToken);
foreach (var unit in player.Units)
{
await _unitService.CreatePlayerUnitAsync(player, unit, dbController, cancellationToken);
}
}
}
}

View File

@@ -0,0 +1,112 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class SurfaceService : IModelService<Surface, int>
{
public async Task CreateAsync(Surface input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Surfaces
(
Image
)
VALUES
(
@IMAGE
); {dbController.GetLastIdSql()}";
input.SurfaceId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO SurfaceDescription
(
SurfaceId,
Code,
Name,
Description
)
VALUES
(
@SURFACE_ID,
@CODE,
@NAME,
@DESCRIPTION
)";
var parameters = new
{
BATTLEGROUND_ID = input.SurfaceId,
CODE = description.Code,
NAME = description.Name,
description = description.Description,
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Surface input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Surfaces WHERE SurfaceId = @SURFACE_ID";
await dbController.QueryAsync(sql, new
{
SURFACE_ID = input.SurfaceId
}, cancellationToken);
}
public static async Task<List<Surface>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Surfaces";
var list = await dbController.SelectDataAsync<Surface>(sql, cancellationToken: cancellationToken);
foreach(var item in list)
{
if(item.Image != null)
{
string base64String = Convert.ToBase64String(item.Image);
item.ConvertedImage = $"data:image/png;base64,{base64String}";
}
}
await LoadSurfaceDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<Surface?> GetAsync(int surfaceId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Surfaces WHERE SurfaceId = @SURFACE_ID";
var surface = await dbController.GetFirstAsync<Surface>(sql, new
{
SURFACE_ID = surfaceId
}, cancellationToken);
return surface;
}
private static async Task LoadSurfaceDescriptionsAsync(List<Surface> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> surfaceIds = list.Select(x => x.SurfaceId);
string sql = $"SELECT * FROM SurfaceDescription WHERE SurfaceId IN ({string.Join(",", surfaceIds)})";
List<SurfaceDescription> descriptions = await dbController.SelectDataAsync<SurfaceDescription>(sql, null, cancellationToken);
foreach (var battleLocation in list)
{
battleLocation.Description = [.. descriptions.Where(x => x.SurfaceId == battleLocation.SurfaceId)];
}
}
}
public Task UpdateAsync(Surface input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,171 @@
using DbController;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class TemplateService : IModelService<Template, int, TemplateFilter>
{
public async Task CreateAsync(Template input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Templates
(
UserId,
FractionId,
Name,
Force,
UsedForce
)
VALUES
(
@USER_ID,
@FRACTION_ID,
@NAME,
@FORCE,
@USED_FORCE
); {dbController.GetLastIdSql()}";
input.TemplateId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach(var unit in input.Units)
{
await UnitService.CreateTemplateUnitAsync(input.TemplateId, unit, dbController, cancellationToken);
}
}
public async Task DeleteAsync(Template input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeleteTemplateUnitsAsync(input.TemplateId, dbController, cancellationToken);
string sql = "DELETE FROM Templates WHERE TemplateId = @TEMPLATE_ID";
await dbController.QueryAsync(sql, new
{
TEMPLATE_ID = input.TemplateId
}, cancellationToken);
}
public async Task<Template?> GetAsync(int templateId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Templates WHERE TemplateId = @TEMPLATE_ID";
var template = await dbController.GetFirstAsync<Template>(sql, new
{
TEMPLATE_ID = templateId
}, cancellationToken);
return template;
}
public static async Task<List<Template>> GetTemplateOnForceAsync(int user_id, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT * FROM Templates WHERE UserId = @USER_ID";
var list = await dbController.SelectDataAsync<Template>(sql, new
{
USER_ID = user_id
}, cancellationToken);
foreach (var item in list)
{
item.Units = await UnitService.GetTemplateUnitsAsync(item.TemplateId, dbController, cancellationToken);
}
return list;
}
public async Task<List<Template>> GetAsync(TemplateFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT * FROM Templates WHERE UserId = @USER_ID");
sb.AppendLine(GetFilterWhere(filter));
sb.AppendLine(@$" ORDER BY Name ASC");
sb.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sb.ToString();
List<Template> list = await dbController.SelectDataAsync<Template>(sql, GetFilterParameter(filter), cancellationToken);
foreach(var item in list)
{
item.Units = await UnitService.GetTemplateUnitsAsync(item.TemplateId, dbController, cancellationToken);
}
return list;
}
public Dictionary<string, object?> GetFilterParameter(TemplateFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "USER_ID", filter.UserId },
{ "FRACTION_ID", filter.FractionId },
{ "FORCE", filter.Force }
};
}
public string GetFilterWhere(TemplateFilter filter)
{
StringBuilder sb = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sb.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
if (filter.FractionId != 0)
{
sb.AppendLine(@" AND FractionId = @FRACTION_ID");
}
if (filter.Force != 0)
{
sb.AppendLine(@" AND Force = @FORCE");
}
string sql = sb.ToString();
return sql;
}
public async Task<int> GetTotalAsync(TemplateFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT COUNT(*) FROM Templates WHERE UserId = @USER_ID");
sb.AppendLine(GetFilterWhere(filter));
string sql = sb.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public async Task UpdateAsync(Template input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeleteTemplateUnitsAsync(input.TemplateId, dbController, cancellationToken);
string sql = @"UPDATE Templates SET
FractionId = @FRACTION_ID,
Name = @NAME,
Force = @FORCE,
UsedForce = @USED_FORCE
WHERE TemplateId = @TEMPLATE_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var unit in input.Units)
{
await UnitService.CreateTemplateUnitAsync(input.TemplateId, unit, dbController, cancellationToken);
}
}
}
}

View File

@@ -0,0 +1,393 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class UnitService(WeaponService weaponService) : IModelService<Unit, int, UnitFilter>
{
private readonly WeaponService _weaponService = weaponService;
public async Task CreateAsync(Unit input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Units
(
FractionId,
ClassId,
TroopQuantity,
Defense,
Moving,
PrimaryWeaponId,
SecondaryWeaponId,
FistAbilityId,
SecondAbilityId
)
VALUES
(
@FRACTION_ID,
@CLASS_ID,
@TROOP_QUANTITY,
@DEFENSE,
@MOVING,
@PRIMARY_WEAPON_ID,
@SECONDARY_WEAPON_ID,
@FIRST_ABILITY_ID,
@SECOND_ABILITY_ID
); {dbController.GetLastIdSql()}";
input.UnitId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO UnitDescription
(
UnitId,
Code,
Name,
Description
)
VALUES
(
@UNIT_ID,
@CODE,
@NAME,
@DESCRIPTION
)";
var parameters = new
{
UNIT_ID = input.UnitId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Unit input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Units WHERE UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, new
{
UNIT_ID = input.UnitId
}, cancellationToken);
}
public async Task DeleteUnitAsync(int userId, int unitId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM UserUnits WHERE UserId = @USER_ID AND UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = userId,
UNIT_ID = unitId
}, cancellationToken);
}
public async Task<Unit?> GetAsync(int unitId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Units WHERE UnitId = @UNIT_ID";
var unit = await dbController.GetFirstAsync<Unit>(sql, new
{
UNIT_ID = unitId
}, cancellationToken);
if (unit?.PrimaryWeaponId != null && unit?.SecondaryWeaponId != null)
{
unit.PrimaryWeapon = await _weaponService.GetAsync((int)unit.PrimaryWeaponId, dbController, cancellationToken) ?? new();
unit.SecondaryWeapon = await _weaponService.GetAsync((int)unit.SecondaryWeaponId, dbController, cancellationToken) ?? new();
}
return unit;
}
public static async Task<List<Unit>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT * FROM Units";
var list = await dbController.SelectDataAsync<Unit>(sql, cancellationToken: cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<List<Unit>> GetAsync(UnitFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT ud.Name, u.* " +
"FROM UnitDescription ud " +
"INNER JOIN Units u " +
"ON (u.UnitId = ud.UnitId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sqlBuilder.ToString();
List<Unit> list = await dbController.SelectDataAsync<Unit>(sql, GetFilterParameter(filter), cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public Dictionary<string, object?> GetFilterParameter(UnitFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "FRACTION_ID", filter.FractionId },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
public string GetFilterWhere(UnitFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
if (filter.FractionId != 0)
{
sqlBuilder.AppendLine(@" AND FractionId = @FRACTION_ID");
}
string sql = sqlBuilder.ToString();
return sql;
}
public async Task<int> GetTotalAsync(UnitFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM UnitDescription ud JOIN Units u ON ud.UnitId = u.UnitId WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public static async Task<List<Unit>> GetUserUnitsAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT uu.Quantity, u.*
FROM UserUnits uu
INNER JOIN Units u ON (u.UnitId = uu.UnitId)
WHERE uu.UserId = @USER_ID";
var list = await dbController.SelectDataAsync<Unit>(sql, new
{
USER_ID = userId
}, cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<List<Unit>> GetPlayerUnitsAsync(int playerId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT pu.Quantity, u.*
FROM PlayerUnits pu
INNER JOIN Units u ON (u.UnitId = pu.UnitId)
WHERE pu.PlayerId = @PLAYER_ID";
var list = await dbController.SelectDataAsync<Unit>(sql, new
{
PLAYER_ID = playerId
}, cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public static async Task<List<Unit>> GetTemplateUnitsAsync(int templateId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT tu.Quantity, u.*
FROM TemplateUnits tu
INNER JOIN Units u ON (u.UnitId = tu.UnitId)
WHERE tu.TemplateId = @TEMPLATE_ID";
var list = await dbController.SelectDataAsync<Unit>(sql, new
{
TEMPLATE_ID = templateId
}, cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task CreateUserUnitAsync(User user, Unit unit, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM UserUnits WHERE UserId = @USER_ID AND UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId,
UNIT_ID = unit.UnitId
}, cancellationToken);
sql = @"INSERT INTO UserUnits
(
UserId,
UnitId,
Quantity
)
VALUES
(
@USER_ID,
@UNIT_ID,
@QUANTITY
)";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId,
UNIT_ID = unit.UnitId,
QUANTITY = unit.Quantity
}, cancellationToken);
}
public static async Task DeleteTemplateUnitsAsync(int templateId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM TemplateUnits WHERE TemplateId = @TEMPLATE_ID";
await dbController.QueryAsync(sql, new
{
TEMPLATE_ID = templateId
}, cancellationToken);
}
public static async Task DeletePlayerUnitsAsync(int playerid, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM PlayerUnits WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, new
{
PLAYER_ID = playerid
}, cancellationToken);
}
public static async Task CreateTemplateUnitAsync(int templateId, Unit unit, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"INSERT INTO TemplateUnits
(
TemplateId,
UnitId,
Quantity
)
VALUES
(
@TEMPLATE_ID,
@UNIT_ID,
@QUANTITY
)";
await dbController.QueryAsync(sql, new
{
TEMPLATE_ID = templateId,
UNIT_ID = unit.UnitId,
QUANTITY = unit.Quantity
}, cancellationToken);
}
public async Task CreatePlayerUnitAsync(Player Input, Unit unit, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"INSERT INTO PlayerUnits
(
PlayerId,
UnitId,
Quantity
)
VALUES
(
@PLAYER_ID,
@UNIT_ID,
@QUANTITY
)";
await dbController.QueryAsync(sql, new
{
PLAYER_ID = Input.PlayerId,
UNIT_ID = unit.UnitId,
QUANTITY = unit.Quantity
}, cancellationToken);
}
public async Task UpdateAsync(Unit input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Units SET
FractionId = @FRACTION_ID,
ClassId = @CLASS_ID,
TroopQuantity = @TROOP_QUANTITY,
Defense = @DEFENSE,
Moving = @MOVING,
PrimaryWeaponId = @PRIMARY_WEAPON_ID,
SecondaryWeaponId = @SECONDARY_WEAPON_ID,
FirstAbilityId = @FIRST_ABILITY_ID,
SecondAbilityId = @SECOND_ABILITY_ID
WHERE UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE UnitDescription SET
Name = @NAME,
Description = @DESCRIPTION,
Mechanic = @MECHANIC
WHERE UnitId = @UNIT_ID AND Code = @CODE";
var parameters = new
{
UNIT_ID = input.UnitId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description,
MECHANIC = description.Mechanic
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
private static async Task LoadUnitDescriptionsAsync(List<Unit> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> unitIds = list.Select(x => x.UnitId);
string sql = $"SELECT * FROM UnitDescription WHERE UnitId IN ({string.Join(",", unitIds)})";
List<UnitDescription> descriptions = await dbController.SelectDataAsync<UnitDescription>(sql, null, cancellationToken);
foreach (var unit in list)
{
unit.Description = [.. descriptions.Where(x => x.UnitId == unit.UnitId)];
}
}
}
}
}

View File

@@ -0,0 +1,298 @@
using DbController;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class UserService(PermissionService permissionService) : IModelService<User, int, UserFilter>
{
private readonly PermissionService _permissionService = permissionService;
public async Task CreateAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
input.LastLogin = DateTime.Now;
input.RegistrationDate = DateTime.Now;
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Users
(
Username,
DisplayName,
Password,
Salt,
LastLogin,
RegistrationDate
)
VALUES
(
@USERNAME,
@DISPLAY_NAME,
@PASSWORD,
@SALT,
@LAST_LOGIN,
@REGISTRATION_DATE
); {dbController.GetLastIdSql()}";
input.UserId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
await _permissionService.UpdateUserPermissionsAsync(input, dbController, cancellationToken);
}
public async Task DeleteAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Users WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = input.UserId
}, cancellationToken);
}
public static async Task DeleteFriendAsync(int userId, int friendId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM UserFriends WHERE UserId = @USER_ID AND FriendId = @FRIEND_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = userId,
FRIEND_ID = friendId
}, cancellationToken);
}
public async Task<User?> GetAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Users WHERE UserId = @USER_ID";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
if (user is not null)
{
user.Permissions = await PermissionService.GetUserPermissionsAsync(user.UserId, dbController, cancellationToken);
}
return user;
}
public static async Task<User?> GetAsync(string username, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Users WHERE UPPER(username) = UPPER(@USERNAME)";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USERNAME = username
}, cancellationToken);
if (user is not null)
{
user.Permissions = await PermissionService.GetUserPermissionsAsync(user.UserId, dbController, cancellationToken);
}
return user;
}
public async Task<User?> GetUserInformationAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT UserId, Username, DisplayName, Image FROM Users WHERE UserId = @USER_ID";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
return user;
}
public async Task<List<User>> GetAsync(UserFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.Append("SELECT UserId, Username, DisplayName, Description, MainFractionId, LastLogin, RegistrationDate, Image FROM Users WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@$" ORDER BY UserId DESC");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<User> list = await dbController.SelectDataAsync<User>(sql, GetFilterParameter(filter), cancellationToken);
// Berechtigungen müssen noch geladen werden
List<Permission> permissions = await PermissionService.GetAllAsync(dbController);
sql = "SELECT * FROM UserPermissions";
List<UserPermission> user_permissions = await dbController.SelectDataAsync<UserPermission>(sql, null, cancellationToken);
foreach (var user in list)
{
List<UserPermission> permissions_for_user = [.. user_permissions.Where(x => x.UserId == user.UserId)];
List<int> permission_ids = [.. permissions_for_user.Select(x => x.PermissionId)];
user.Permissions = [.. permissions.Where(x => permission_ids.Contains(x.PermissionId))];
}
return list;
}
public Dictionary<string, object?> GetFilterParameter(UserFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" }
};
}
public string GetFilterWhere(UserFilter filter)
{
StringBuilder sb = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sb.AppendLine(@" AND
(
UPPER(DisplayName) LIKE @SEARCHPHRASE
OR UPPER(Username) LIKE @SEARCHPHRASE
)");
}
string sql = sb.ToString();
return sql;
}
public async Task<int> GetTotalAsync(UserFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) FROM Users WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public static async Task<List<User>> GetUserFriendsAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT u.UserId, u.Username, u.DisplayName, u.Image
FROM Users AS u
JOIN UserFriends AS uf ON u.UserId = uf.FriendId
WHERE uf.UserId = @USER_ID";
var list = await dbController.SelectDataAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
return list;
}
public static async Task<bool> CheckUserFriendAsync(int userId, int friendId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT
CASE
WHEN EXISTS (
SELECT 1
FROM UserFriends
WHERE UserId = @USER_ID AND FriendId = @FRIEND_ID
) THEN 'true'
ELSE 'false'
END AS IsFriend;";
bool isfriend = await dbController.GetFirstAsync<bool>(sql, new
{
USER_ID = userId,
FRIEND_ID = friendId
}, cancellationToken);
return isfriend;
}
public async Task UpdateAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Users SET
Username = @USERNAME,
DisplayName = @DISPLAY_NAME,
Description = @DESCRIPTION,
MainFractionId = @MAIN_FRACTION_ID
WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
await _permissionService.UpdateUserPermissionsAsync(input, dbController, cancellationToken);
}
public async Task UpdateLastLoginAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
input.LastLogin = DateTime.Now;
string sql = @"UPDATE Users SET
LastLogin = @LAST_LOGIN
WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
public static async Task<bool> FirstUserExistsAsync(IDbController dbController)
{
string sql = "SELECT * FROM Users";
var tmp = await dbController.GetFirstAsync<object>(sql);
return tmp != null;
}
public async Task<string> GetUsernameAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT Username FROM Users WHERE UserId = @USER_ID";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
if (user != null)
{
return user.Username;
}
else
{
return string.Empty;
}
}
public static async Task CreateUserFriendAsync(int userId, int friendId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"INSERT INTO UserFriends
(
UserId,
FriendId
)
VALUES
(
@USER_ID,
@FRIEND_ID
)";
await dbController.QueryAsync(sql, new
{
USER_ID = userId,
FRIEND_ID = friendId
}, cancellationToken);
}
}
}

View File

@@ -0,0 +1,215 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class WeaponService : IModelService<Weapon, int, WeaponFilter>
{
public async Task CreateAsync(Weapon input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Weapons
(
Attack,
Quality,
Range,
Dices
)
VALUES
(
@ATTACK,
@QUALITY,
@RANGE,
@DICES
); {dbController.GetLastIdSql()}";
input.WeaponId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO WeaponDescription
(
WeaponId,
Code,
Name,
Description
)
VALUES
(
@WEAPON_ID,
@CODE,
@NAME,
@DESCRIPTION
)";
var parameters = new
{
WEAPON_ID = input.WeaponId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Weapon input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Weapons WHERE WeaponId = @WEAPON_ID";
await dbController.QueryAsync(sql, new
{
WEAPON_ID = input.WeaponId
}, cancellationToken);
}
public async Task<Weapon?> GetAsync(int weaponId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Weapons WHERE WeaponId = @WEAPON_ID";
var weapon = await dbController.GetFirstAsync<Weapon>(sql, new
{
WEAPON_ID = weaponId
}, cancellationToken);
if(weapon != null)
{
await LoadWeaponDescriptionAsync(weapon, dbController, cancellationToken);
}
return weapon;
}
public async Task<List<Weapon>> GetAsync(WeaponFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT wd.Name, w.* " +
"FROM WeaponDescription wd " +
"INNER JOIN Weapons w " +
"ON (w.WeaponId = wd.WeaponId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sqlBuilder.ToString();
List<Weapon> list = await dbController.SelectDataAsync<Weapon>(sql, GetFilterParameter(filter), cancellationToken);
await LoadWeaponDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public Dictionary<string, object?> GetFilterParameter(WeaponFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
public string GetFilterWhere(WeaponFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public async Task<int> GetTotalAsync(WeaponFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM WeaponDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public static async Task<List<Weapon>> GetAllAsync(IDbController dbController)
{
string sql = "SELECT * FROM Weapons";
var list = await dbController.SelectDataAsync<Weapon>(sql);
await LoadWeaponDescriptionsAsync(list, dbController);
return list;
}
public async Task UpdateAsync(Weapon input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Weapons SET
Attack = @ATTACK,
Quality = @QUALITY,
Range = @RANGE,
Dices = @DICES
WHERE WeaponId = @WEAPON_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE WeaponDescription SET
Name = @NAME,
Description = @DESCRIPTION
WHERE WeaponId = @WEAPON_ID AND Code = @CODE";
var parameters = new
{
WEAPON_ID = input.WeaponId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
private static async Task LoadWeaponDescriptionsAsync(List<Weapon> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> weaponIds = list.Select(x => x.WeaponId);
string sql = $"SELECT * FROM WeaponDescription WHERE WeaponId IN ({string.Join(",", weaponIds)})";
List<WeaponDescription> descriptions = await dbController.SelectDataAsync<WeaponDescription>(sql, null, cancellationToken);
foreach (var weapon in list)
{
weapon.Description = descriptions.Where(x => x.WeaponId == weapon.WeaponId).ToList();
}
}
}
private static async Task LoadWeaponDescriptionAsync(Weapon weapon, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $"SELECT * FROM WeaponDescription WHERE WeaponId IN @WEAPON_ID";
weapon.Description = await dbController.SelectDataAsync<WeaponDescription>(sql, new
{
WEAPON_ID = weapon.WeaponId
}, cancellationToken);
}
}
}

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Pages\**" />
<EmbeddedResource Remove="Pages\**" />
<None Remove="Pages\**" />
</ItemGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DbController" Version="5.0.0" />
<PackageReference Include="DbController.SqlServer" Version="5.0.0" />
<PackageReference Include="DbController.TypeHandler" Version="5.0.0" />
<PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class AbilityValidator : AbstractValidator<Ability>
{
public AbilityValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Mechanic)
.MaximumLength(1000)
.WithMessage("Mechanic must contain only 1000 characters.");
}
}
}

View File

@@ -0,0 +1,28 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class FractionValidator : AbstractValidator<Fraction>
{
public FractionValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).ShortName)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(3)
.WithMessage("Name must contain only 3 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
}
}
}

View File

@@ -0,0 +1,32 @@
using FluentValidation;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class GameValidator : AbstractValidator<Game>
{
public GameValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(30)
.WithMessage("Name must contain only 30 characters.");
RuleFor(x => x.GamemodeId)
.NotEmpty()
.WithMessage("Game Mode must be selected");
When(x => x.GamemodeId is 1 or 4, () =>
{
RuleFor(x => x.NumberOfRounds)
.NotNull()
.WithMessage("Rounds must be selected");
});
RuleFor(x => x.Force)
.NotEmpty()
.WithMessage("Force must be selected");
}
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class GamemodeValidator : AbstractValidator<Gamemode>
{
public GamemodeValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Mechanic)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
}
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class PlayerValidator : AbstractValidator<Player>
{
public PlayerValidator()
{
RuleFor(x => x.UsedForce)
.LessThanOrEqualTo(x => x.AllowedForce)
.WithMessage("Force points over limit");
RuleFor(x => x.Units)
.NotEmpty()
.WithMessage("At least one unit must be selected");
}
}
}

View File

@@ -0,0 +1,29 @@
using FluentValidation;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class TemplateValidator : AbstractValidator<Template>
{
public TemplateValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.FractionId)
.NotEmpty()
.WithMessage("Faction must be selected");
RuleFor(x => x.Force)
.NotEmpty()
.WithMessage("Force must be selected");
RuleFor(x => x.UsedForce)
.LessThanOrEqualTo(x => x.Force)
.WithMessage("Force points over limit");
}
}
}

View File

@@ -0,0 +1,42 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class UnitValidator : AbstractValidator<Unit>
{
public UnitValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.FractionId)
.NotEmpty()
.WithMessage("Faction must be selected");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(500)
.WithMessage("Description must contain only 500 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Mechanic)
.MaximumLength(500)
.WithMessage("Description must contain only 500 characters.");
RuleFor(x => x.Defense)
.NotEmpty()
.WithMessage("Defense must be filled.");
RuleFor(x => x.Moving)
.NotEmpty()
.WithMessage("The field must be filled")
.LessThan(30)
.WithMessage("The field may be a maximum of 30")
.GreaterThan(0)
.WithMessage("The field must not be greater than 0");
}
}
}

View File

@@ -0,0 +1,38 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class WeaponValidator : AbstractValidator<Weapon>
{
public WeaponValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(500)
.WithMessage("Description must contain only 500 characters.");
RuleFor(x => x.Attack)
.NotEmpty()
.WithMessage("Attack must be filled.");
RuleFor(x => x.Quality)
.NotEmpty()
.WithMessage("Quality must be filled");
RuleFor(x => x.Range)
.NotEmpty()
.WithMessage("Range must be filled");
RuleFor(x => x.Dices)
.NotEmpty()
.WithMessage("Dices must be filled");
}
}
}

View File

@@ -0,0 +1,11 @@
{
"Filter": {
"PageLimit": 30
},
"Sql": {
"ConnectionString": "Server=localhost\\SQLEXPRESS;Database=Tabletop;Trusted_Connection=True;TrustServerCertificate=True;"
},
"IpGeolocation": {
"ApiKey": "43d1738db9eb411cb959972585b331f6"
}
}