feat: builders, specifications
This commit is contained in:
@@ -3,7 +3,31 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35303.130
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bahla.Tests", "src\Bahla.Tests\Bahla.Tests.csproj", "{F1392CEF-BD63-4208-8ADA-95DF407E0D28}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bahla.Domain", "src\Bahla.Domain\Bahla.Domain.csproj", "{543F323C-31BF-4BEA-9503-F9A26E963FC8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bahla.Persistence", "src\Bahla.Persistence\Bahla.Persistence.csproj", "{FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{543F323C-31BF-4BEA-9503-F9A26E963FC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{543F323C-31BF-4BEA-9503-F9A26E963FC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{543F323C-31BF-4BEA-9503-F9A26E963FC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{543F323C-31BF-4BEA-9503-F9A26E963FC8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
|
||||
9
src/Bahla.Domain/Bahla.Domain.csproj
Normal file
9
src/Bahla.Domain/Bahla.Domain.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
13
src/Bahla.Domain/Builders/Base/IEntityBuilder.cs
Normal file
13
src/Bahla.Domain/Builders/Base/IEntityBuilder.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Bahla.Domain.Entities.Base;
|
||||
|
||||
namespace Bahla.Domain.Builders.Base
|
||||
{
|
||||
public interface IEntityBuilder<out T> where T : IEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Build the entity.
|
||||
/// </summary>
|
||||
/// <returns>Entity</returns>
|
||||
T Build();
|
||||
}
|
||||
}
|
||||
37
src/Bahla.Domain/Builders/Base/IRoleBuilder.cs
Normal file
37
src/Bahla.Domain/Builders/Base/IRoleBuilder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Bahla.Domain.Entities;
|
||||
using Bahla.Domain.Types;
|
||||
|
||||
namespace Bahla.Domain.Builders.Base
|
||||
{
|
||||
public interface IRoleBuilder : IEntityBuilder<Role>
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the identifier for the role, if not provided, a new identifier will be generated.
|
||||
/// </summary>
|
||||
/// <param name="identifier">Identifier</param>
|
||||
/// <returns>Builder</returns>
|
||||
IRoleBuilder WithIdentifier(Identifier identifier);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the name for the role.
|
||||
/// </summary>
|
||||
/// <param name="roleName">Role name</param>
|
||||
/// <returns>Builder</returns>
|
||||
IRoleBuilder WithName(string roleName);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value mask for the role. Maximum allowed is 32 bits (1 << 32).
|
||||
/// </summary>
|
||||
/// <param name="valueMask">Role bitmask</param>
|
||||
/// <returns>Builder</returns>
|
||||
IRoleBuilder WithValueMask(uint valueMask);
|
||||
|
||||
/// <summary>
|
||||
/// Clones the role.
|
||||
/// </summary>
|
||||
/// <param name="role">Role</param>
|
||||
/// <returns>Builder</returns>
|
||||
IRoleBuilder Clone(Role role);
|
||||
}
|
||||
}
|
||||
61
src/Bahla.Domain/Builders/RoleBuilder.cs
Normal file
61
src/Bahla.Domain/Builders/RoleBuilder.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Bahla.Domain.Builders.Base;
|
||||
using Bahla.Domain.Entities;
|
||||
using Bahla.Domain.Exceptions;
|
||||
using Bahla.Domain.Specifications.Base;
|
||||
using Bahla.Domain.Types;
|
||||
|
||||
namespace Bahla.Domain.Builders
|
||||
{
|
||||
internal sealed class RoleNameIsNotEmptySpecification : CompositeSpecification<RoleBuilder>
|
||||
{
|
||||
public override bool IsSatisfiedBy(RoleBuilder entity)
|
||||
=> !string.IsNullOrEmpty(entity._roleName) && !string.IsNullOrWhiteSpace(entity._roleName);
|
||||
}
|
||||
|
||||
|
||||
public sealed class RoleBuilder : IRoleBuilder
|
||||
{
|
||||
internal Identifier _identifier = Identifier.Generate;
|
||||
|
||||
internal string _roleName = string.Empty;
|
||||
|
||||
internal uint _valueMask;
|
||||
|
||||
public Role Build()
|
||||
{
|
||||
if (new RoleNameIsNotEmptySpecification()
|
||||
.IsSatisfiedBy(this)) {
|
||||
return new Role(this);
|
||||
}
|
||||
|
||||
throw new EntityBuilderException("Role name cannot be null or empty");
|
||||
}
|
||||
|
||||
public IRoleBuilder WithIdentifier(Identifier identifier)
|
||||
{
|
||||
_identifier = identifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRoleBuilder WithName(string roleName)
|
||||
{
|
||||
_roleName = roleName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRoleBuilder WithValueMask(uint valueMask)
|
||||
{
|
||||
_valueMask = valueMask;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IRoleBuilder Clone(Role role)
|
||||
{
|
||||
_identifier = role.UUID;
|
||||
_roleName = role.RoleName;
|
||||
_valueMask = role.ValueMask;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/Bahla.Domain/Entities/Base/Entity.cs
Normal file
6
src/Bahla.Domain/Entities/Base/Entity.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Bahla.Domain.Types;
|
||||
|
||||
namespace Bahla.Domain.Entities.Base
|
||||
{
|
||||
public abstract record Entity(Identifier UUID) : IEntity;
|
||||
}
|
||||
15
src/Bahla.Domain/Entities/Base/IEntity.cs
Normal file
15
src/Bahla.Domain/Entities/Base/IEntity.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Bahla.Domain.Types;
|
||||
|
||||
namespace Bahla.Domain.Entities.Base
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface that describes an independent entity.
|
||||
/// </summary>
|
||||
public interface IEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for the entity.
|
||||
/// </summary>
|
||||
Identifier UUID { get; init; }
|
||||
}
|
||||
}
|
||||
26
src/Bahla.Domain/Entities/Role.cs
Normal file
26
src/Bahla.Domain/Entities/Role.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Bahla.Domain.Builders;
|
||||
using Bahla.Domain.Builders.Base;
|
||||
using Bahla.Domain.Entities.Base;
|
||||
using Bahla.Domain.Types;
|
||||
|
||||
namespace Bahla.Domain.Entities
|
||||
{
|
||||
public sealed record Role : Entity
|
||||
{
|
||||
public string RoleName { get; init; }
|
||||
public uint ValueMask { get; init; }
|
||||
internal Role(Identifier identifier, string roleName, uint valueMask) : base(identifier)
|
||||
{
|
||||
RoleName = roleName;
|
||||
ValueMask = valueMask;
|
||||
}
|
||||
|
||||
internal Role(RoleBuilder builder) : base(builder._identifier)
|
||||
{
|
||||
RoleName = builder._roleName;
|
||||
ValueMask = builder._valueMask;
|
||||
}
|
||||
|
||||
public static IRoleBuilder Builder => new RoleBuilder();
|
||||
}
|
||||
}
|
||||
4
src/Bahla.Domain/Exceptions/DomainException.cs
Normal file
4
src/Bahla.Domain/Exceptions/DomainException.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace Bahla.Domain.Exceptions
|
||||
{
|
||||
public abstract class DomainException(string message) : Exception(string.Concat("Domain Exception: ", " ", message)) { }
|
||||
}
|
||||
7
src/Bahla.Domain/Exceptions/EntityBuilderException.cs
Normal file
7
src/Bahla.Domain/Exceptions/EntityBuilderException.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Bahla.Domain.Exceptions
|
||||
{
|
||||
public sealed class EntityBuilderException : DomainException
|
||||
{
|
||||
internal EntityBuilderException(string message) : base(message) { }
|
||||
}
|
||||
}
|
||||
14
src/Bahla.Domain/Specifications/AndNotSpecification.cs
Normal file
14
src/Bahla.Domain/Specifications/AndNotSpecification.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Bahla.Domain.Entities.Base;
|
||||
using Bahla.Domain.Specifications.Base;
|
||||
|
||||
namespace Bahla.Domain.Specifications
|
||||
{
|
||||
public sealed class AndNotSpecification<T>(ISpecification<T> left, ISpecification<T> right) : CompositeSpecification<T> where T : class
|
||||
{
|
||||
private readonly ISpecification<T> _left = left;
|
||||
private readonly ISpecification<T> _right = right;
|
||||
|
||||
public override bool IsSatisfiedBy(T entity)
|
||||
=> _left.IsSatisfiedBy(entity) && !_right.IsSatisfiedBy(entity);
|
||||
}
|
||||
}
|
||||
12
src/Bahla.Domain/Specifications/AndSpecification.cs
Normal file
12
src/Bahla.Domain/Specifications/AndSpecification.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Bahla.Domain.Specifications.Base;
|
||||
|
||||
namespace Bahla.Domain.Specifications
|
||||
{
|
||||
public sealed class AndSpecification<T>(ISpecification<T> left, ISpecification<T> right) : CompositeSpecification<T> where T : class
|
||||
{
|
||||
private readonly ISpecification<T> _left = left;
|
||||
private readonly ISpecification<T> _right = right;
|
||||
public override bool IsSatisfiedBy(T entity)
|
||||
=> _left.IsSatisfiedBy(entity) && _right.IsSatisfiedBy(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Bahla.Domain.Specifications.Base
|
||||
{
|
||||
public abstract class CompositeSpecification<T> : ISpecification<T> where T : class
|
||||
{
|
||||
public abstract bool IsSatisfiedBy(T entity);
|
||||
public ISpecification<T> And(ISpecification<T> other)
|
||||
=> new AndSpecification<T>(this, other);
|
||||
public ISpecification<T> AndNot(ISpecification<T> other)
|
||||
=> new AndNotSpecification<T>(this, other);
|
||||
public ISpecification<T> Not()
|
||||
=> new NotSpecification<T>(this);
|
||||
public ISpecification<T> Or(ISpecification<T> other)
|
||||
=> new OrSpecification<T>(this, other);
|
||||
public ISpecification<T> OrNot(ISpecification<T> other)
|
||||
=> new OrNotSpecification<T>(this, other);
|
||||
}
|
||||
}
|
||||
13
src/Bahla.Domain/Specifications/Base/ISpecification.cs
Normal file
13
src/Bahla.Domain/Specifications/Base/ISpecification.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bahla.Domain.Specifications.Base
|
||||
{
|
||||
public interface ISpecification<T> where T : class
|
||||
{
|
||||
bool IsSatisfiedBy(T entity);
|
||||
ISpecification<T> And(ISpecification<T> other);
|
||||
ISpecification<T> AndNot(ISpecification<T> other);
|
||||
ISpecification<T> Or(ISpecification<T> other);
|
||||
ISpecification<T> OrNot(ISpecification<T> other);
|
||||
ISpecification<T> Not();
|
||||
|
||||
}
|
||||
}
|
||||
12
src/Bahla.Domain/Specifications/NotSpecification.cs
Normal file
12
src/Bahla.Domain/Specifications/NotSpecification.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Bahla.Domain.Entities.Base;
|
||||
using Bahla.Domain.Specifications.Base;
|
||||
|
||||
namespace Bahla.Domain.Specifications
|
||||
{
|
||||
public sealed class NotSpecification<T>(ISpecification<T> other) : CompositeSpecification<T> where T : class
|
||||
{
|
||||
private readonly ISpecification<T> _other = other;
|
||||
public override bool IsSatisfiedBy(T entity)
|
||||
=> !_other.IsSatisfiedBy(entity);
|
||||
}
|
||||
}
|
||||
14
src/Bahla.Domain/Specifications/OrNotSpecification.cs
Normal file
14
src/Bahla.Domain/Specifications/OrNotSpecification.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Bahla.Domain.Entities.Base;
|
||||
using Bahla.Domain.Specifications.Base;
|
||||
|
||||
namespace Bahla.Domain.Specifications
|
||||
{
|
||||
public sealed class OrNotSpecification<T>(ISpecification<T> left, ISpecification<T> right) : CompositeSpecification<T> where T : class
|
||||
{
|
||||
private readonly ISpecification<T> _left = left;
|
||||
private readonly ISpecification<T> _right = right;
|
||||
|
||||
public override bool IsSatisfiedBy(T entity)
|
||||
=> _left.IsSatisfiedBy(entity) || !_right.IsSatisfiedBy(entity);
|
||||
}
|
||||
}
|
||||
13
src/Bahla.Domain/Specifications/OrSpecification.cs
Normal file
13
src/Bahla.Domain/Specifications/OrSpecification.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Bahla.Domain.Specifications.Base;
|
||||
|
||||
namespace Bahla.Domain.Specifications
|
||||
{
|
||||
public sealed class OrSpecification<T>(ISpecification<T> left, ISpecification<T> right) : CompositeSpecification<T> where T : class
|
||||
{
|
||||
private readonly ISpecification<T> _left = left;
|
||||
private readonly ISpecification<T> _right = right;
|
||||
|
||||
public override bool IsSatisfiedBy(T entity)
|
||||
=> _left.IsSatisfiedBy(entity) || _right.IsSatisfiedBy(entity);
|
||||
}
|
||||
}
|
||||
12
src/Bahla.Domain/Types/Identifier.cs
Normal file
12
src/Bahla.Domain/Types/Identifier.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Bahla.Domain.Types
|
||||
{
|
||||
public readonly record struct Identifier(Guid UUID) : IComparable<Identifier>
|
||||
{
|
||||
public static implicit operator Identifier(Guid uuid) => new(uuid);
|
||||
public static implicit operator Identifier(string uuid) => new(Guid.Parse(uuid));
|
||||
public static Identifier Generate => Guid.NewGuid();
|
||||
public override string ToString() => UUID.ToString();
|
||||
public int CompareTo(Identifier other)
|
||||
=> UUID.CompareTo(other.UUID);
|
||||
}
|
||||
}
|
||||
13
src/Bahla.Persistence/Bahla.Persistence.csproj
Normal file
13
src/Bahla.Persistence/Bahla.Persistence.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Base\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
7
src/Bahla.Persistence/Class1.cs
Normal file
7
src/Bahla.Persistence/Class1.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Bahla.Persistence
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
@@ -17,8 +17,17 @@
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Bahla.Domain\Bahla.Domain.csproj" />
|
||||
<ProjectReference Include="..\Bahla.Persistence\Bahla.Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Entities\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
44
src/Bahla.Tests/Builders/BuilderTests.cs
Normal file
44
src/Bahla.Tests/Builders/BuilderTests.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Bahla.Domain.Entities;
|
||||
using Bahla.Domain.Exceptions;
|
||||
|
||||
namespace Bahla.Tests.Builders
|
||||
{
|
||||
internal class BuilderTests
|
||||
{
|
||||
|
||||
[Test, Description("Test to create a role through builder.")]
|
||||
public void TestRoleBuilder()
|
||||
{
|
||||
var role = Role.Builder
|
||||
.WithName("Administrator")
|
||||
.WithValueMask(1 << 0)
|
||||
.Build();
|
||||
|
||||
var clonedRole = Role.Builder
|
||||
.Clone(role)
|
||||
.Build();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(role.RoleName, Is.EqualTo(clonedRole.RoleName));
|
||||
Assert.That(role.ValueMask, Is.EqualTo(clonedRole.ValueMask));
|
||||
Assert.That(role.UUID, Is.EqualTo(clonedRole.UUID));
|
||||
|
||||
Assert.That(clonedRole, Is.Not.SameAs(role));
|
||||
});
|
||||
}
|
||||
|
||||
[Test, Description("Test to create a role with a empty or null name.")]
|
||||
public void TestRoleBuilderBadName()
|
||||
{
|
||||
Assert.Throws<EntityBuilderException>(() =>
|
||||
{
|
||||
Role.Builder
|
||||
.WithName("")
|
||||
.WithValueMask(1 << 0)
|
||||
.Build();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Bahla.Tests/Types/TypeIdentifierTests.cs
Normal file
39
src/Bahla.Tests/Types/TypeIdentifierTests.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Bahla.Domain.Types;
|
||||
|
||||
namespace Bahla.Tests.Entities
|
||||
{
|
||||
internal class TypeIdentifierTests
|
||||
{
|
||||
const string StringIdentifier = "316f94a7-fb3b-44f0-abe1-88c2a3c99109";
|
||||
const string StringIdentifier2 = "2c304fa1-19a4-432d-a729-73bdd45586ed";
|
||||
|
||||
[Test]
|
||||
public void TestAssignmentGuid()
|
||||
{
|
||||
Identifier firstIdentifier = Guid.Parse(StringIdentifier);
|
||||
Identifier secondIdentifier = Guid.Parse(StringIdentifier2);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(firstIdentifier, Is.Not.EqualTo(secondIdentifier));
|
||||
Assert.That(firstIdentifier.ToString(), Is.EqualTo(StringIdentifier));
|
||||
Assert.That(secondIdentifier.ToString(), Is.EqualTo(StringIdentifier2));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAssignmentString()
|
||||
{
|
||||
Identifier firstIdentifier = StringIdentifier;
|
||||
Identifier secondIdentifier = StringIdentifier2;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(firstIdentifier, Is.Not.EqualTo(secondIdentifier));
|
||||
Assert.That(firstIdentifier.ToString(), Is.EqualTo(StringIdentifier));
|
||||
Assert.That(secondIdentifier.ToString(), Is.EqualTo(StringIdentifier2));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Bahla.Tests
|
||||
{
|
||||
public class Tests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test1()
|
||||
{
|
||||
Assert.Pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user