From b73fd8eb05fb31cb24193f53ef37990ed950872c Mon Sep 17 00:00:00 2001 From: Andrija Stevanovic Date: Wed, 4 Feb 2026 21:11:51 +0100 Subject: [PATCH] init backend --- .gitignore | 32 ++++++++ dotnet/.gitignore | 77 +++++++++++++++++++ dotnet/AipsCore/AipsCore.csproj | 9 +++ .../Application/Abstract/Command/ICommand.cs | 5 ++ .../Abstract/Command/ICommandHandler.cs | 13 ++++ .../Application/Abstract/Query/IQuery.cs | 3 + .../Abstract/Query/IQueryHandler.cs | 7 ++ .../Domain/Abstract/Rule/AbstractRule.cs | 18 +++++ dotnet/AipsCore/Domain/Abstract/Rule/IRule.cs | 10 +++ .../ValueObject/AbstractValueObject.cs | 24 ++++++ .../Common/Validation/Rules/CharsetRule.cs | 31 ++++++++ .../Common/Validation/Rules/EmailRule.cs | 32 ++++++++ .../Common/Validation/Rules/ExactLength.cs | 24 ++++++ .../Common/Validation/Rules/MaxLengthRule.cs | 24 ++++++ .../Common/Validation/Rules/MinLengthRule.cs | 24 ++++++ .../Common/Validation/ValidationError.cs | 3 + .../Common/Validation/ValidationException.cs | 13 ++++ .../Domain/Common/Validation/Validator.cs | 55 +++++++++++++ .../Domain/Common/ValueObjects/DomainId.cs | 22 ++++++ .../Domain/Common/ValueObjects/Email.cs | 26 +++++++ dotnet/AipsCore/Domain/Models/User/User.cs | 18 +++++ .../User/Validation/UsernameCharsetRule.cs | 26 +++++++ .../Domain/Models/User/ValueObjects/UserId.cs | 5 ++ .../Models/User/ValueObjects/Username.cs | 33 ++++++++ .../Validation/WhitebordCodeCharsetRule.cs | 20 +++++ .../Whiteboard/ValueObjects/WhiteboardCode.cs | 31 ++++++++ .../Whiteboard/ValueObjects/WhiteboardId.cs | 5 ++ .../Domain/Models/Whiteboard/Whiteboard.cs | 18 +++++ .../Infrastructure/Db/AipsDbContext.cs | 6 ++ dotnet/AipsCore/Utility/Text/Charset.cs | 41 ++++++++++ dotnet/dotnet.sln | 18 +++++ 31 files changed, 673 insertions(+) create mode 100644 .gitignore create mode 100644 dotnet/.gitignore create mode 100644 dotnet/AipsCore/AipsCore.csproj create mode 100644 dotnet/AipsCore/Application/Abstract/Command/ICommand.cs create mode 100644 dotnet/AipsCore/Application/Abstract/Command/ICommandHandler.cs create mode 100644 dotnet/AipsCore/Application/Abstract/Query/IQuery.cs create mode 100644 dotnet/AipsCore/Application/Abstract/Query/IQueryHandler.cs create mode 100644 dotnet/AipsCore/Domain/Abstract/Rule/AbstractRule.cs create mode 100644 dotnet/AipsCore/Domain/Abstract/Rule/IRule.cs create mode 100644 dotnet/AipsCore/Domain/Abstract/ValueObject/AbstractValueObject.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/Rules/CharsetRule.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/Rules/EmailRule.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/Rules/ExactLength.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/Rules/MaxLengthRule.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/Rules/MinLengthRule.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/ValidationError.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/ValidationException.cs create mode 100644 dotnet/AipsCore/Domain/Common/Validation/Validator.cs create mode 100644 dotnet/AipsCore/Domain/Common/ValueObjects/DomainId.cs create mode 100644 dotnet/AipsCore/Domain/Common/ValueObjects/Email.cs create mode 100644 dotnet/AipsCore/Domain/Models/User/User.cs create mode 100644 dotnet/AipsCore/Domain/Models/User/Validation/UsernameCharsetRule.cs create mode 100644 dotnet/AipsCore/Domain/Models/User/ValueObjects/UserId.cs create mode 100644 dotnet/AipsCore/Domain/Models/User/ValueObjects/Username.cs create mode 100644 dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhitebordCodeCharsetRule.cs create mode 100644 dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs create mode 100644 dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardId.cs create mode 100644 dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.cs create mode 100644 dotnet/AipsCore/Infrastructure/Db/AipsDbContext.cs create mode 100644 dotnet/AipsCore/Utility/Text/Charset.cs create mode 100644 dotnet/dotnet.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bb609c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# .NET +dotnet/**/bin/ +dotnet/**/obj/ +dotnet/**/*.user +dotnet/**/*.vs/ +dotnet/**/.vs/ + +# React/Node +react/node_modules/ +react/dist/ +react/.env +react/.env.local +react/.env.*.local + +# IDE +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln.docstates + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* + +# Claude Code plans +.claude/ diff --git a/dotnet/.gitignore b/dotnet/.gitignore new file mode 100644 index 0000000..d8ac706 --- /dev/null +++ b/dotnet/.gitignore @@ -0,0 +1,77 @@ +# Environment variables with secrets +.env + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio cache/options directory +.vs/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# NuGet Packages +*.nupkg +*.snupkg +**/packages/* +!**/packages/build/ +*.nuget.props +*.nuget.targets + +# Docker +**/.dockerignore diff --git a/dotnet/AipsCore/AipsCore.csproj b/dotnet/AipsCore/AipsCore.csproj new file mode 100644 index 0000000..0e42146 --- /dev/null +++ b/dotnet/AipsCore/AipsCore.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/dotnet/AipsCore/Application/Abstract/Command/ICommand.cs b/dotnet/AipsCore/Application/Abstract/Command/ICommand.cs new file mode 100644 index 0000000..91f1225 --- /dev/null +++ b/dotnet/AipsCore/Application/Abstract/Command/ICommand.cs @@ -0,0 +1,5 @@ +namespace AipsCore.Application.Abstract.Command; + +public interface ICommand {} + +public interface ICommand {} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Abstract/Command/ICommandHandler.cs b/dotnet/AipsCore/Application/Abstract/Command/ICommandHandler.cs new file mode 100644 index 0000000..06e69dc --- /dev/null +++ b/dotnet/AipsCore/Application/Abstract/Command/ICommandHandler.cs @@ -0,0 +1,13 @@ +namespace AipsCore.Application.Abstract.Command; + +public interface ICommandHandler + where TCommand : ICommand +{ + Task Handle(TCommand command, CancellationToken cancellationToken = default); +} + +public interface ICommandHandler + where TCommand : ICommand +{ + Task Handle(TCommand command, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Abstract/Query/IQuery.cs b/dotnet/AipsCore/Application/Abstract/Query/IQuery.cs new file mode 100644 index 0000000..bbb3de3 --- /dev/null +++ b/dotnet/AipsCore/Application/Abstract/Query/IQuery.cs @@ -0,0 +1,3 @@ +namespace AipsCore.Application.Abstract.Query; + +public interface IQuery {} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Abstract/Query/IQueryHandler.cs b/dotnet/AipsCore/Application/Abstract/Query/IQueryHandler.cs new file mode 100644 index 0000000..d3e8632 --- /dev/null +++ b/dotnet/AipsCore/Application/Abstract/Query/IQueryHandler.cs @@ -0,0 +1,7 @@ +namespace AipsCore.Application.Abstract.Query; + +public interface IQueryHandler + where TQuery : IQuery +{ + Task HandleAsync(TQuery query, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Abstract/Rule/AbstractRule.cs b/dotnet/AipsCore/Domain/Abstract/Rule/AbstractRule.cs new file mode 100644 index 0000000..d1f2530 --- /dev/null +++ b/dotnet/AipsCore/Domain/Abstract/Rule/AbstractRule.cs @@ -0,0 +1,18 @@ +using AipsCore.Domain.Common.Validation; + +namespace AipsCore.Domain.Abstract.Rule; + +public abstract class AbstractRule : IRule +{ + protected abstract string ErrorCode { get; } + protected abstract string ErrorMessage { get; } + + public string ValueObjectName { protected get; set; } = "Unknown"; + + public ValidationError GetError() + { + return new ValidationError(ErrorCode, ErrorMessage); + } + + public abstract bool Validate(); +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Abstract/Rule/IRule.cs b/dotnet/AipsCore/Domain/Abstract/Rule/IRule.cs new file mode 100644 index 0000000..c77ba62 --- /dev/null +++ b/dotnet/AipsCore/Domain/Abstract/Rule/IRule.cs @@ -0,0 +1,10 @@ +using AipsCore.Domain.Common.Validation; + +namespace AipsCore.Domain.Abstract.Rule; + +public interface IRule +{ + ValidationError GetError(); + bool Validate(); + string ValueObjectName { set; } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Abstract/ValueObject/AbstractValueObject.cs b/dotnet/AipsCore/Domain/Abstract/ValueObject/AbstractValueObject.cs new file mode 100644 index 0000000..910fa2f --- /dev/null +++ b/dotnet/AipsCore/Domain/Abstract/ValueObject/AbstractValueObject.cs @@ -0,0 +1,24 @@ +using AipsCore.Domain.Abstract.Rule; +using AipsCore.Domain.Common.Validation; + +namespace AipsCore.Domain.Abstract.ValueObject; + +public abstract record AbstractValueObject +{ + protected abstract ICollection GetValidationRules(); + + protected void Validate() + { + var rules = GetValidationRules(); + var validator = new Validator(rules, ValueObjectName); + + validator.Validate(); + + if (!validator.Success) + { + throw validator.GetValidationException(); + } + } + + private string ValueObjectName => this.GetType().Name; +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/CharsetRule.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/CharsetRule.cs new file mode 100644 index 0000000..ef2cf6a --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/CharsetRule.cs @@ -0,0 +1,31 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; + +namespace AipsCore.Domain.Common.Validation.Rules; + +public class CharsetRule : AbstractRule +{ + private readonly string _stringValue; + private readonly char[] _charset; + + protected CharsetRule(string stringValue, char[] charset) + { + _stringValue = stringValue; + _charset = charset; + } + + protected override string ErrorCode => "charset"; + protected override string ErrorMessage => $"Forbidden characters in '{ValueObjectName}'"; + + public override bool Validate() + { + foreach (char character in _stringValue) + { + if (!_charset.Contains(character)) + { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/EmailRule.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/EmailRule.cs new file mode 100644 index 0000000..a7b34c5 --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/EmailRule.cs @@ -0,0 +1,32 @@ +using System.Net.Mail; +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; + +namespace AipsCore.Domain.Common.Validation.Rules; + +public class EmailRule : AbstractRule +{ + protected override string ErrorCode => "email_invalid"; + protected override string ErrorMessage => "Email is not in the valid format"; + + private readonly string _emailValue; + + public EmailRule(string emailValue) + { + _emailValue = emailValue; + } + + public override bool Validate() + { + try + { + _ = new MailAddress(_emailValue); + } + catch + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/ExactLength.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/ExactLength.cs new file mode 100644 index 0000000..4e884df --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/ExactLength.cs @@ -0,0 +1,24 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; + +namespace AipsCore.Domain.Common.Validation.Rules; + +public class ExactLength : AbstractRule +{ + private readonly string _stringValue; + private readonly int _exactLentgh; + protected override string ErrorCode => "exact_length"; + protected override string ErrorMessage + => $"Length of '{ValueObjectName}' must be {_exactLentgh} characters"; + + public ExactLength(string stringValue, int exactLentgh) + { + _stringValue = stringValue; + _exactLentgh = exactLentgh; + } + + public override bool Validate() + { + return _stringValue.Length == _exactLentgh; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/MaxLengthRule.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/MaxLengthRule.cs new file mode 100644 index 0000000..798e177 --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/MaxLengthRule.cs @@ -0,0 +1,24 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; + +namespace AipsCore.Domain.Common.Validation.Rules; + +public class MaxLengthRule : AbstractRule +{ + private readonly string _stringValue; + private readonly int _maximumLentgh; + protected override string ErrorCode => "minimum_length"; + protected override string ErrorMessage + => $"Length of '{ValueObjectName}' must be at most {_maximumLentgh} characters"; + + public MaxLengthRule(string stringValue, int maximumLentgh) + { + _stringValue = stringValue; + _maximumLentgh = maximumLentgh; + } + + public override bool Validate() + { + return _stringValue.Length <= _maximumLentgh; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/MinLengthRule.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/MinLengthRule.cs new file mode 100644 index 0000000..2f9fd1d --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/MinLengthRule.cs @@ -0,0 +1,24 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; + +namespace AipsCore.Domain.Common.Validation.Rules; + +public class MinLengthRule : AbstractRule +{ + private readonly string _stringValue; + private readonly int _minimumLentgh; + protected override string ErrorCode => "minimum_length"; + protected override string ErrorMessage + => $"Length of '{ValueObjectName}' must be at least {_minimumLentgh} characters"; + + public MinLengthRule(string stringValue, int minimumLentgh) + { + _stringValue = stringValue; + _minimumLentgh = minimumLentgh; + } + + public override bool Validate() + { + return _stringValue.Length >= _minimumLentgh; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/ValidationError.cs b/dotnet/AipsCore/Domain/Common/Validation/ValidationError.cs new file mode 100644 index 0000000..bab4969 --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/ValidationError.cs @@ -0,0 +1,3 @@ +namespace AipsCore.Domain.Common.Validation; + +public record ValidationError(string Code, string Message); \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/ValidationException.cs b/dotnet/AipsCore/Domain/Common/Validation/ValidationException.cs new file mode 100644 index 0000000..6378716 --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/ValidationException.cs @@ -0,0 +1,13 @@ +using System.Collections.Immutable; + +namespace AipsCore.Domain.Common.Validation; + +public class ValidationException : Exception +{ + public ICollection ValidationErrors { get; private init; } + + public ValidationException(ICollection validationErrors) + { + ValidationErrors = validationErrors; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/Validation/Validator.cs b/dotnet/AipsCore/Domain/Common/Validation/Validator.cs new file mode 100644 index 0000000..34f0072 --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/Validation/Validator.cs @@ -0,0 +1,55 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; + +namespace AipsCore.Domain.Common.Validation; + +public class Validator +{ + private readonly ICollection _validationRules; + private readonly string _valueObjectName; + + public Validator(ICollection validationRules, string valueObjectName) + { + _validationRules = validationRules; + _valueObjectName = valueObjectName; + } + + public bool Success { get; private set; } = false; + + private ValidationException? _validationException = null; + + public ValidationException GetValidationException() + { + if (_validationException is null) + { + return new ValidationException([]); + } + return _validationException; + } + + public void Validate() + { + List errors = []; + + foreach (var validationRule in _validationRules) + { + validationRule.ValueObjectName = _valueObjectName; + + if (!validationRule.Validate()) + { + errors.Add(validationRule.GetError()); + } + } + + if (errors.Any()) + { + Success = false; + _validationException = new ValidationException(errors.ToArray()); + } + else + { + Success = true; + _validationException = null; + } + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/ValueObjects/DomainId.cs b/dotnet/AipsCore/Domain/Common/ValueObjects/DomainId.cs new file mode 100644 index 0000000..6f76d85 --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/ValueObjects/DomainId.cs @@ -0,0 +1,22 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; +using AipsCore.Domain.Abstract.ValueObject; +using AipsCore.Domain.Common.Validation; + +namespace AipsCore.Domain.Common.ValueObjects; + +public record DomainId : AbstractValueObject +{ + public string IdValue { get; init; } + + public DomainId(string IdValue) + { + this.IdValue = IdValue; + Validate(); + } + + protected override ICollection GetValidationRules() + { + return []; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Common/ValueObjects/Email.cs b/dotnet/AipsCore/Domain/Common/ValueObjects/Email.cs new file mode 100644 index 0000000..3da6daf --- /dev/null +++ b/dotnet/AipsCore/Domain/Common/ValueObjects/Email.cs @@ -0,0 +1,26 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; +using AipsCore.Domain.Abstract.ValueObject; +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Common.Validation.Rules; + +namespace AipsCore.Domain.Common.ValueObjects; + +public record Email : AbstractValueObject +{ + public string EmailValue { get; init; } + + public Email(string EmailValue) + { + this.EmailValue = EmailValue; + Validate(); + } + + protected override ICollection GetValidationRules() + { + return + [ + new EmailRule(EmailValue) + ]; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/User/User.cs b/dotnet/AipsCore/Domain/Models/User/User.cs new file mode 100644 index 0000000..1ca404c --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/User/User.cs @@ -0,0 +1,18 @@ +using AipsCore.Domain.Common.ValueObjects; +using AipsCore.Domain.Models.User.ValueObjects; + +namespace AipsCore.Domain.Models.User; + +public class User +{ + public UserId Id { get; private set; } + public Email Email { get; private set; } + public Username Username { get; private set; } + + public User(UserId id, Email email, Username username) + { + Id = id; + Email = email; + Username = username; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/User/Validation/UsernameCharsetRule.cs b/dotnet/AipsCore/Domain/Models/User/Validation/UsernameCharsetRule.cs new file mode 100644 index 0000000..59fa851 --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/User/Validation/UsernameCharsetRule.cs @@ -0,0 +1,26 @@ +using AipsCore.Domain.Common.Validation.Rules; +using AipsCore.Utility.Text; + +namespace AipsCore.Domain.Models.User.Validation; + +public class UsernameCharsetRule : CharsetRule +{ + public UsernameCharsetRule(string stringValue) + : base(stringValue, GetUsernameCharset()) + { + } + + private static char[] GetUsernameCharset() + { + var alphanumericCharset = Charset.GetAlphanumericCharset(); + + char[] usernameCharset = [..alphanumericCharset, '_']; + + return usernameCharset; + } + + protected override string ErrorCode => "username_charset"; + + protected override string ErrorMessage => + "Username contains invalid characters, only alphanumeric characters and '_' are allowed"; +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/User/ValueObjects/UserId.cs b/dotnet/AipsCore/Domain/Models/User/ValueObjects/UserId.cs new file mode 100644 index 0000000..022011e --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/User/ValueObjects/UserId.cs @@ -0,0 +1,5 @@ +using AipsCore.Domain.Common.ValueObjects; + +namespace AipsCore.Domain.Models.User.ValueObjects; + +public record UserId(string IdValue) : DomainId(IdValue); \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/User/ValueObjects/Username.cs b/dotnet/AipsCore/Domain/Models/User/ValueObjects/Username.cs new file mode 100644 index 0000000..8b2f6f1 --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/User/ValueObjects/Username.cs @@ -0,0 +1,33 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; +using AipsCore.Domain.Abstract.ValueObject; +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Common.Validation.Rules; +using AipsCore.Domain.Common.ValueObjects; +using AipsCore.Domain.Models.User.Validation; + +namespace AipsCore.Domain.Models.User.ValueObjects; + +public record Username : AbstractValueObject +{ + public string UsernameValue { get; init; } + + public Username(string UsernameValue) + { + this.UsernameValue = UsernameValue; + Validate(); + } + + private const int MinimumLength = 8; + private const int MaximumLength = 20; + + protected override ICollection GetValidationRules() + { + return + [ + new MinLengthRule(UsernameValue, MinimumLength), + new MaxLengthRule(UsernameValue, MaximumLength), + new UsernameCharsetRule(UsernameValue) + ]; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhitebordCodeCharsetRule.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhitebordCodeCharsetRule.cs new file mode 100644 index 0000000..2234acc --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhitebordCodeCharsetRule.cs @@ -0,0 +1,20 @@ +using AipsCore.Domain.Common.Validation.Rules; +using AipsCore.Utility.Text; + +namespace AipsCore.Domain.Models.Whiteboard.Validation; + +public class WhitebordCodeCharsetRule : CharsetRule +{ + public WhitebordCodeCharsetRule(string stringValue) + : base(stringValue, GetWhiteboardCodeCharset()) + { + } + + private static char[] GetWhiteboardCodeCharset() + { + return Charset.GetNumericCharset(); + } + + protected override string ErrorCode => "whiteboard_code_charset"; + protected override string ErrorMessage => "Whiteboard code must contain only numbers"; +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs new file mode 100644 index 0000000..3e7b30a --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs @@ -0,0 +1,31 @@ +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Abstract.Rule; +using AipsCore.Domain.Abstract.ValueObject; +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Common.Validation.Rules; +using AipsCore.Domain.Common.ValueObjects; +using AipsCore.Domain.Models.Whiteboard.Validation; + +namespace AipsCore.Domain.Models.Whiteboard.ValueObjects; + +public record WhiteboardCode : AbstractValueObject +{ + public string CodeValue { get; init; } + + public WhiteboardCode(string CodeValue) + { + this.CodeValue = CodeValue; + Validate(); + } + + private const int CodeLength = 8; + + protected override ICollection GetValidationRules() + { + return + [ + new ExactLength(CodeValue, CodeLength), + new WhitebordCodeCharsetRule(CodeValue) + ]; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardId.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardId.cs new file mode 100644 index 0000000..a9a46aa --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardId.cs @@ -0,0 +1,5 @@ +using AipsCore.Domain.Common.ValueObjects; + +namespace AipsCore.Domain.Models.Whiteboard.ValueObjects; + +public record WhiteboardId(string IdValue) : DomainId(IdValue); \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.cs new file mode 100644 index 0000000..1fbb366 --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.cs @@ -0,0 +1,18 @@ +using AipsCore.Domain.Models.User.ValueObjects; +using AipsCore.Domain.Models.Whiteboard.ValueObjects; + +namespace AipsCore.Domain.Models.Whiteboard; + +public class Whiteboard +{ + public WhiteboardId Id { get; private set; } + public UserId WhiteboardOwnerId { get; private set; } + public WhiteboardCode Code { get; private set; } + + public Whiteboard(WhiteboardId id, User.User whiteboardOwner, WhiteboardCode code) + { + Id = id; + WhiteboardOwnerId = whiteboardOwner.Id; + Code = code; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/Db/AipsDbContext.cs b/dotnet/AipsCore/Infrastructure/Db/AipsDbContext.cs new file mode 100644 index 0000000..ab85fb3 --- /dev/null +++ b/dotnet/AipsCore/Infrastructure/Db/AipsDbContext.cs @@ -0,0 +1,6 @@ +namespace AipsCore.Infrastructure.Db; + +public class AipsDbContext +{ + +} \ No newline at end of file diff --git a/dotnet/AipsCore/Utility/Text/Charset.cs b/dotnet/AipsCore/Utility/Text/Charset.cs new file mode 100644 index 0000000..ab91cb3 --- /dev/null +++ b/dotnet/AipsCore/Utility/Text/Charset.cs @@ -0,0 +1,41 @@ +namespace AipsCore.Utility.Text; + +public static class Charset +{ + public static char[] GetNumericCharset() + { + var charset = new List(); + + // 0–9 + for (char c = '0'; c <= '9'; c++) + charset.Add(c); + + return charset.ToArray(); + } + + public static char[] GetLettersCharset() + { + + var charset = new List(); + + // a–z + for (char c = 'a'; c <= 'z'; c++) + charset.Add(c); + + // A–Z + for (char c = 'A'; c <= 'Z'; c++) + charset.Add(c); + + return charset.ToArray(); + } + + public static char[] GetAlphanumericCharset() + { + var lettersCharset = GetLettersCharset(); + var numericCharset = GetNumericCharset(); + + char[] alphanumericCharset = [..numericCharset, ..lettersCharset]; + + return alphanumericCharset; + } +} \ No newline at end of file diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln new file mode 100644 index 0000000..6c32bfc --- /dev/null +++ b/dotnet/dotnet.sln @@ -0,0 +1,18 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E0259F3A-496C-4A5E-8D6D-D134F2A9E3D4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AipsCore", "AipsCore\AipsCore.csproj", "{C2EAA516-A045-4525-9A47-4AA8198FD104}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C2EAA516-A045-4525-9A47-4AA8198FD104}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2EAA516-A045-4525-9A47-4AA8198FD104}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2EAA516-A045-4525-9A47-4AA8198FD104}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2EAA516-A045-4525-9A47-4AA8198FD104}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal