diff --git a/.editorconfig b/.editorconfig
index 19d08bc..3d9637a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -91,4 +91,13 @@ dotnet_diagnostic.SA1500.severity = error
csharp_style_namespace_declarations = file_scoped:warning
# Disable XML docs
-dotnet_diagnostic.CS1591.severity = none
\ No newline at end of file
+dotnet_diagnostic.CS1591.severity = none
+
+dotnet_diagnostic.CA1062.severity = none
+dotnet_diagnostic.CA1034.severity = none
+
+
+dotnet_diagnostic.CA2012.severity = none
+
+[/src/Lab5/ATM.Infrastructure.DataAccess/Migrations/*.cs]
+dotnet_diagnostic.SA1649.severity = none
diff --git a/Directory.Build.props b/Directory.Build.props
index 53408c2..8543b2c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,6 +3,7 @@
net7.0
disable
enable
+ true
diff --git a/Directory.Packages.props b/Directory.Packages.props
index dff3479..327678f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,12 +5,16 @@
+
+
+
+
diff --git a/Itmo.ObjectOrientedProgramming.sln b/Itmo.ObjectOrientedProgramming.sln
index f7e1daa..d2f9075 100644
--- a/Itmo.ObjectOrientedProgramming.sln
+++ b/Itmo.ObjectOrientedProgramming.sln
@@ -21,8 +21,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab3", "src\Lab3\Lab3.cspro
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab4", "src\Lab4\Lab4.csproj", "{A53D3DBD-FA2D-47B4-90A9-B44AF07ADE71}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lab5", "Lab5", "{55C309C9-B7DE-4F64-AFDE-A33F9C2AB2D4}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab2.Tests", "tests\Lab2.Tests\Lab2.Tests.csproj", "{25D0E1F0-760B-4E76-B0D9-3EF274B35AF6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab3.Tests", "tests\Lab3.Tests\Lab3.Tests.csproj", "{7E5E46DB-EF55-47E6-822A-6AE20BF2FCD9}"
@@ -31,6 +29,34 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab4.Tests", "tests\Lab4.Te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab5.Tests", "tests\Lab5.Tests\Lab5.Tests.csproj", "{FA627A62-AD7A-4A72-B15C-EC8AEFD206DB}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lab5", "Lab5", "{99CB5FCA-AC39-4AD0-B4FF-A8C051CC2809}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{08E62A91-F7B4-4FE8-A1F8-BFD64F7E3A9E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{D6F0AF28-4B74-4CEF-BAE0-631C60979762}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{A882AA78-975B-4BF0-9193-5A0C5365974C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutomatedTellerMachine", "src\Lab5\AutomatedTellerMachine\AutomatedTellerMachine.csproj", "{9BAA271C-E0CC-42C8-83E7-FC398D53874F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATM.Application", "src\Lab5\ATM.Application\ATM.Application.csproj", "{6A71BA8C-6A36-4B80-9E97-4F99C8BFF1E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATM.Application.Models", "src\Lab5\ATM.Application.Models\ATM.Application.Models.csproj", "{8469CC97-19D1-4ABA-9213-99F03DA8BBF2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATM.Application.Abstractions", "src\Lab5\ATM.Application.Abstractions\ATM.Application.Abstractions.csproj", "{249A7B78-E81D-4127-901F-207159D59BCB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATM.Application.Contracts", "src\Lab5\ATM.Application.Contracts\ATM.Application.Contracts.csproj", "{D1EA8947-0596-48FF-A5AF-B6B51C63A484}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATM.Infrastructure.DataAccess", "src\Lab5\ATM.Infrastructure.DataAccess\ATM.Infrastructure.DataAccess.csproj", "{9B57FEE1-2A6D-49D8-8C48-AA3993BD1F82}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{E078567B-7E99-44F0-BE59-861B99056106}"
+ ProjectSection(SolutionItems) = preProject
+ src\Lab5\.env = src\Lab5\.env
+ src\Lab5\docker-compose.yaml = src\Lab5\docker-compose.yaml
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATM.Presentation.Console", "src\Lab5\ATM.Presentation.Console\ATM.Presentation.Console.csproj", "{581A4324-47EC-4DF7-9839-F58450EA54F0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,11 +68,22 @@ Global
{433B9342-A6BF-4194-8F08-727AADAE7504} = {8ED774F4-D429-4B82-B768-50DCA8EFF609}
{43B4CD48-18D5-4EDE-80CB-86460F4C64CB} = {8ED774F4-D429-4B82-B768-50DCA8EFF609}
{A53D3DBD-FA2D-47B4-90A9-B44AF07ADE71} = {8ED774F4-D429-4B82-B768-50DCA8EFF609}
- {55C309C9-B7DE-4F64-AFDE-A33F9C2AB2D4} = {8ED774F4-D429-4B82-B768-50DCA8EFF609}
{25D0E1F0-760B-4E76-B0D9-3EF274B35AF6} = {792FFE57-F1A0-47A4-9E2A-80F75FF2F8DB}
{7E5E46DB-EF55-47E6-822A-6AE20BF2FCD9} = {792FFE57-F1A0-47A4-9E2A-80F75FF2F8DB}
{17689E4A-FE4F-4893-8E28-3BA911DEC8D8} = {792FFE57-F1A0-47A4-9E2A-80F75FF2F8DB}
{FA627A62-AD7A-4A72-B15C-EC8AEFD206DB} = {792FFE57-F1A0-47A4-9E2A-80F75FF2F8DB}
+ {99CB5FCA-AC39-4AD0-B4FF-A8C051CC2809} = {8ED774F4-D429-4B82-B768-50DCA8EFF609}
+ {08E62A91-F7B4-4FE8-A1F8-BFD64F7E3A9E} = {99CB5FCA-AC39-4AD0-B4FF-A8C051CC2809}
+ {D6F0AF28-4B74-4CEF-BAE0-631C60979762} = {99CB5FCA-AC39-4AD0-B4FF-A8C051CC2809}
+ {A882AA78-975B-4BF0-9193-5A0C5365974C} = {99CB5FCA-AC39-4AD0-B4FF-A8C051CC2809}
+ {9BAA271C-E0CC-42C8-83E7-FC398D53874F} = {99CB5FCA-AC39-4AD0-B4FF-A8C051CC2809}
+ {6A71BA8C-6A36-4B80-9E97-4F99C8BFF1E9} = {D6F0AF28-4B74-4CEF-BAE0-631C60979762}
+ {8469CC97-19D1-4ABA-9213-99F03DA8BBF2} = {D6F0AF28-4B74-4CEF-BAE0-631C60979762}
+ {249A7B78-E81D-4127-901F-207159D59BCB} = {D6F0AF28-4B74-4CEF-BAE0-631C60979762}
+ {D1EA8947-0596-48FF-A5AF-B6B51C63A484} = {D6F0AF28-4B74-4CEF-BAE0-631C60979762}
+ {9B57FEE1-2A6D-49D8-8C48-AA3993BD1F82} = {A882AA78-975B-4BF0-9193-5A0C5365974C}
+ {E078567B-7E99-44F0-BE59-861B99056106} = {21E20419-0C74-46BC-9D7D-A24CEEC29ACC}
+ {581A4324-47EC-4DF7-9839-F58450EA54F0} = {08E62A91-F7B4-4FE8-A1F8-BFD64F7E3A9E}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EABA4888-E6C1-4ACC-856E-FBD34CEA3EDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -85,5 +122,33 @@ Global
{FA627A62-AD7A-4A72-B15C-EC8AEFD206DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA627A62-AD7A-4A72-B15C-EC8AEFD206DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA627A62-AD7A-4A72-B15C-EC8AEFD206DB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9BAA271C-E0CC-42C8-83E7-FC398D53874F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9BAA271C-E0CC-42C8-83E7-FC398D53874F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9BAA271C-E0CC-42C8-83E7-FC398D53874F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9BAA271C-E0CC-42C8-83E7-FC398D53874F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A71BA8C-6A36-4B80-9E97-4F99C8BFF1E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A71BA8C-6A36-4B80-9E97-4F99C8BFF1E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A71BA8C-6A36-4B80-9E97-4F99C8BFF1E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A71BA8C-6A36-4B80-9E97-4F99C8BFF1E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8469CC97-19D1-4ABA-9213-99F03DA8BBF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8469CC97-19D1-4ABA-9213-99F03DA8BBF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8469CC97-19D1-4ABA-9213-99F03DA8BBF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8469CC97-19D1-4ABA-9213-99F03DA8BBF2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {249A7B78-E81D-4127-901F-207159D59BCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {249A7B78-E81D-4127-901F-207159D59BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {249A7B78-E81D-4127-901F-207159D59BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {249A7B78-E81D-4127-901F-207159D59BCB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1EA8947-0596-48FF-A5AF-B6B51C63A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1EA8947-0596-48FF-A5AF-B6B51C63A484}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1EA8947-0596-48FF-A5AF-B6B51C63A484}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1EA8947-0596-48FF-A5AF-B6B51C63A484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9B57FEE1-2A6D-49D8-8C48-AA3993BD1F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9B57FEE1-2A6D-49D8-8C48-AA3993BD1F82}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9B57FEE1-2A6D-49D8-8C48-AA3993BD1F82}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9B57FEE1-2A6D-49D8-8C48-AA3993BD1F82}.Release|Any CPU.Build.0 = Release|Any CPU
+ {581A4324-47EC-4DF7-9839-F58450EA54F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {581A4324-47EC-4DF7-9839-F58450EA54F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {581A4324-47EC-4DF7-9839-F58450EA54F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {581A4324-47EC-4DF7-9839-F58450EA54F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/Lab5/.env b/src/Lab5/.env
new file mode 100644
index 0000000..7dcf1da
--- /dev/null
+++ b/src/Lab5/.env
@@ -0,0 +1 @@
+COMPOSE_PROJECT_NAME=atm_labwork
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Abstractions/ATM.Application.Abstractions.csproj b/src/Lab5/ATM.Application.Abstractions/ATM.Application.Abstractions.csproj
new file mode 100644
index 0000000..871798a
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/ATM.Application.Abstractions.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net7.0
+ enable
+ enable
+ AutomatedTellerMachine.Abstractions
+
+
+
+
+
+
+
diff --git a/src/Lab5/ATM.Application.Abstractions/Auth/GenerateResult.cs b/src/Lab5/ATM.Application.Abstractions/Auth/GenerateResult.cs
new file mode 100644
index 0000000..9e0c62b
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/Auth/GenerateResult.cs
@@ -0,0 +1,10 @@
+namespace AutomatedTellerMachine.Abstractions.Auth;
+
+public abstract record GenerateResult
+{
+ private GenerateResult() { }
+
+ public sealed record Success(string Token) : GenerateResult;
+
+ public sealed record NotFound : GenerateResult;
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Abstractions/Auth/IAuthenticationService.cs b/src/Lab5/ATM.Application.Abstractions/Auth/IAuthenticationService.cs
new file mode 100644
index 0000000..87580e0
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/Auth/IAuthenticationService.cs
@@ -0,0 +1,7 @@
+namespace AutomatedTellerMachine.Abstractions.Auth;
+
+public interface IAuthenticationService
+{
+ GenerateResult Generate(long id, string hashedPin);
+ ValidationResult Validate(long id, string token);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Abstractions/Auth/ValidationResult.cs b/src/Lab5/ATM.Application.Abstractions/Auth/ValidationResult.cs
new file mode 100644
index 0000000..10db1da
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/Auth/ValidationResult.cs
@@ -0,0 +1,12 @@
+namespace AutomatedTellerMachine.Abstractions.Auth;
+
+public abstract record ValidationResult
+{
+ private ValidationResult() { }
+
+ public sealed record Success : ValidationResult;
+
+ public sealed record TimedOut : ValidationResult;
+
+ public sealed record NotFound : ValidationResult;
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Abstractions/Repositories/IAccountRepository.cs b/src/Lab5/ATM.Application.Abstractions/Repositories/IAccountRepository.cs
new file mode 100644
index 0000000..6e4a43d
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/Repositories/IAccountRepository.cs
@@ -0,0 +1,12 @@
+using AutomatedTellerMachine.Models;
+
+namespace AutomatedTellerMachine.Abstractions.Repositories;
+
+public interface IAccountRepository
+{
+ Account CreateAccount(string hashedPin);
+ double GetBalanceByAccountId(long id);
+
+ void Debit(long id, double amount);
+ void Credit(long id, double amount);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Abstractions/Repositories/IOperationsRepository.cs b/src/Lab5/ATM.Application.Abstractions/Repositories/IOperationsRepository.cs
new file mode 100644
index 0000000..8a6476a
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/Repositories/IOperationsRepository.cs
@@ -0,0 +1,9 @@
+using AutomatedTellerMachine.Models;
+
+namespace AutomatedTellerMachine.Abstractions.Repositories;
+
+public interface IOperationsRepository
+{
+ IEnumerable GetOperationsByAccount(long id);
+ void AddOperation(long id, double amount);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Abstractions/Repositories/IPinRepository.cs b/src/Lab5/ATM.Application.Abstractions/Repositories/IPinRepository.cs
new file mode 100644
index 0000000..5625128
--- /dev/null
+++ b/src/Lab5/ATM.Application.Abstractions/Repositories/IPinRepository.cs
@@ -0,0 +1,6 @@
+namespace AutomatedTellerMachine.Abstractions.Repositories;
+
+public interface IPinRepository
+{
+ string GetPinHashByAccountId(long id);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Contracts/ATM.Application.Contracts.csproj b/src/Lab5/ATM.Application.Contracts/ATM.Application.Contracts.csproj
new file mode 100644
index 0000000..9acd01f
--- /dev/null
+++ b/src/Lab5/ATM.Application.Contracts/ATM.Application.Contracts.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net7.0
+ enable
+ enable
+ AutomatedTellerMachine.Contracts
+
+
+
+
+
+
+
diff --git a/src/Lab5/ATM.Application.Contracts/Accounts/IAccountService.cs b/src/Lab5/ATM.Application.Contracts/Accounts/IAccountService.cs
new file mode 100644
index 0000000..32a227c
--- /dev/null
+++ b/src/Lab5/ATM.Application.Contracts/Accounts/IAccountService.cs
@@ -0,0 +1,16 @@
+using AutomatedTellerMachine.Models;
+
+namespace AutomatedTellerMachine.Contracts.Accounts;
+
+public interface IAccountService
+{
+ LoginResult Login(long id, string hashedPin);
+
+ double GetBalance(long id, string token);
+
+ OperationResult Credit(double amount, long id, string token);
+ OperationResult Debit(double amount, long id, string token);
+
+ IEnumerable GetOperations(long id, string token);
+ long Create(string hashedPin);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Contracts/Accounts/LoginResult.cs b/src/Lab5/ATM.Application.Contracts/Accounts/LoginResult.cs
new file mode 100644
index 0000000..64c7a71
--- /dev/null
+++ b/src/Lab5/ATM.Application.Contracts/Accounts/LoginResult.cs
@@ -0,0 +1,10 @@
+namespace AutomatedTellerMachine.Contracts.Accounts;
+
+public abstract record LoginResult
+{
+ private LoginResult() { }
+
+ public sealed record Success(string Token) : LoginResult;
+
+ public sealed record NotFound : LoginResult;
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Contracts/Accounts/OperationResult.cs b/src/Lab5/ATM.Application.Contracts/Accounts/OperationResult.cs
new file mode 100644
index 0000000..4d3b5ec
--- /dev/null
+++ b/src/Lab5/ATM.Application.Contracts/Accounts/OperationResult.cs
@@ -0,0 +1,10 @@
+namespace AutomatedTellerMachine.Contracts.Accounts;
+
+public abstract record OperationResult
+{
+ private OperationResult() { }
+
+ public sealed record Success : OperationResult;
+
+ public sealed record Failure : OperationResult;
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Contracts/Admin/IAdminService.cs b/src/Lab5/ATM.Application.Contracts/Admin/IAdminService.cs
new file mode 100644
index 0000000..06f2b41
--- /dev/null
+++ b/src/Lab5/ATM.Application.Contracts/Admin/IAdminService.cs
@@ -0,0 +1,8 @@
+using AutomatedTellerMachine.Contracts.Accounts;
+
+namespace AutomatedTellerMachine.Contracts.Admin;
+
+public interface IAdminService
+{
+ LoginResult AdminLogin(string hashPass);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Models/ATM.Application.Models.csproj b/src/Lab5/ATM.Application.Models/ATM.Application.Models.csproj
new file mode 100644
index 0000000..5cace48
--- /dev/null
+++ b/src/Lab5/ATM.Application.Models/ATM.Application.Models.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net7.0
+ enable
+ enable
+ AutomatedTellerMachine.Models
+
+
+
diff --git a/src/Lab5/ATM.Application.Models/Account.cs b/src/Lab5/ATM.Application.Models/Account.cs
new file mode 100644
index 0000000..b4f9f71
--- /dev/null
+++ b/src/Lab5/ATM.Application.Models/Account.cs
@@ -0,0 +1,3 @@
+namespace AutomatedTellerMachine.Models;
+
+public record Account(long Id);
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application.Models/Operation.cs b/src/Lab5/ATM.Application.Models/Operation.cs
new file mode 100644
index 0000000..78ed25e
--- /dev/null
+++ b/src/Lab5/ATM.Application.Models/Operation.cs
@@ -0,0 +1,3 @@
+namespace AutomatedTellerMachine.Models;
+
+public record Operation(long Id, double Amount);
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application/ATM.Application.csproj b/src/Lab5/ATM.Application/ATM.Application.csproj
new file mode 100644
index 0000000..8a9d583
--- /dev/null
+++ b/src/Lab5/ATM.Application/ATM.Application.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net7.0
+ enable
+ enable
+ AutomatedTellerMachine.Application
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab5/ATM.Application/Accounts/AccountService.cs b/src/Lab5/ATM.Application/Accounts/AccountService.cs
new file mode 100644
index 0000000..e6344fc
--- /dev/null
+++ b/src/Lab5/ATM.Application/Accounts/AccountService.cs
@@ -0,0 +1,74 @@
+using AutomatedTellerMachine.Abstractions.Auth;
+using AutomatedTellerMachine.Abstractions.Repositories;
+using AutomatedTellerMachine.Contracts.Accounts;
+using AutomatedTellerMachine.Models;
+
+namespace AutomatedTellerMachine.Application.Accounts;
+
+public class AccountService : IAccountService
+{
+ private readonly IAuthenticationService _authenticationService;
+ private readonly IAccountRepository _accountRepository;
+ private readonly IOperationsRepository _operationsRepository;
+
+ public AccountService(IAuthenticationService authenticationService, IAccountRepository accountRepository, IOperationsRepository operationsRepository)
+ {
+ _authenticationService = authenticationService;
+ _accountRepository = accountRepository;
+ _operationsRepository = operationsRepository;
+ }
+
+ public LoginResult Login(long id, string hashedPin)
+ {
+ GenerateResult token = _authenticationService.Generate(id, hashedPin);
+
+ return token switch
+ {
+ GenerateResult.Success success => new LoginResult.Success(success.Token),
+ _ => new LoginResult.NotFound(),
+ };
+ }
+
+ public double GetBalance(long id, string token)
+ {
+ _authenticationService.Validate(id, token);
+
+ return _accountRepository.GetBalanceByAccountId(id);
+ }
+
+ public IEnumerable GetOperations(long id, string token)
+ {
+ _authenticationService.Validate(id, token);
+
+ return _operationsRepository.GetOperationsByAccount(id);
+ }
+
+ public OperationResult Credit(double amount, long id, string token)
+ {
+ _authenticationService.Validate(id, token);
+
+ double balance = _accountRepository.GetBalanceByAccountId(id);
+
+ if (balance < amount) return new OperationResult.Failure();
+
+ _operationsRepository.AddOperation(id, -amount);
+ _accountRepository.Credit(id, amount);
+ return new OperationResult.Success();
+ }
+
+ public OperationResult Debit(double amount, long id, string token)
+ {
+ _authenticationService.Validate(id, token);
+
+ _operationsRepository.AddOperation(id, amount);
+
+ _accountRepository.Debit(id, amount);
+ Console.WriteLine(amount);
+ return new OperationResult.Success();
+ }
+
+ public long Create(string hashedPin)
+ {
+ return _accountRepository.CreateAccount(hashedPin).Id;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application/Admin/AdminService.cs b/src/Lab5/ATM.Application/Admin/AdminService.cs
new file mode 100644
index 0000000..cf7edcc
--- /dev/null
+++ b/src/Lab5/ATM.Application/Admin/AdminService.cs
@@ -0,0 +1,29 @@
+using AutomatedTellerMachine.Abstractions.Auth;
+using AutomatedTellerMachine.Abstractions.Repositories;
+using AutomatedTellerMachine.Contracts.Accounts;
+using AutomatedTellerMachine.Contracts.Admin;
+
+namespace AutomatedTellerMachine.Application.Admin;
+
+public class AdminService : IAdminService
+{
+ private readonly IAccountRepository _accountRepository;
+ private readonly IAuthenticationService _authenticationService;
+
+ public AdminService(IAccountRepository accountRepository, IAuthenticationService authenticationService)
+ {
+ _accountRepository = accountRepository;
+ _authenticationService = authenticationService;
+ }
+
+ public LoginResult AdminLogin(string hashPass)
+ {
+ GenerateResult token = _authenticationService.Generate(0, hashPass);
+
+ return token switch
+ {
+ GenerateResult.Success success => new LoginResult.Success(success.Token),
+ _ => new LoginResult.NotFound(),
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application/Auth/AuthenticationService.cs b/src/Lab5/ATM.Application/Auth/AuthenticationService.cs
new file mode 100644
index 0000000..7ef1bb0
--- /dev/null
+++ b/src/Lab5/ATM.Application/Auth/AuthenticationService.cs
@@ -0,0 +1,45 @@
+using AutomatedTellerMachine.Abstractions.Auth;
+using AutomatedTellerMachine.Abstractions.Repositories;
+
+namespace AutomatedTellerMachine.Application.Auth;
+
+public class AuthenticationService : IAuthenticationService
+{
+ private readonly Dictionary _tokens = new();
+ private readonly IPinRepository _pinRepository;
+
+ public AuthenticationService(IPinRepository pinRepository)
+ {
+ _pinRepository = pinRepository;
+ }
+
+ public GenerateResult Generate(long id, string hashedPin)
+ {
+ string localPinHash = _pinRepository.GetPinHashByAccountId(id);
+
+ return localPinHash == hashedPin ? new GenerateResult.Success(GenerateToken(id)) : new GenerateResult.NotFound();
+ }
+
+ public ValidationResult Validate(long id, string token)
+ {
+ if (_tokens[token] != id) return new ValidationResult.NotFound();
+
+ byte[] data = Convert.FromBase64String(token);
+ var when = DateTime.FromBinary(BitConverter.ToInt64(data, 0));
+ if (when < DateTime.UtcNow.AddMinutes(-5))
+ {
+ return new ValidationResult.TimedOut();
+ }
+
+ return new ValidationResult.Success();
+ }
+
+ private string GenerateToken(long id)
+ {
+ byte[] time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());
+ byte[] key = Guid.NewGuid().ToByteArray();
+ string token = Convert.ToBase64String(time.Concat(key).ToArray());
+ _tokens.Add(token, id);
+ return token;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Application/Extensions/ServiceCollectionExtensions.cs b/src/Lab5/ATM.Application/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..009a247
--- /dev/null
+++ b/src/Lab5/ATM.Application/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,18 @@
+using AutomatedTellerMachine.Abstractions.Auth;
+using AutomatedTellerMachine.Application.Accounts;
+using AutomatedTellerMachine.Application.Auth;
+using AutomatedTellerMachine.Contracts.Accounts;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AutomatedTellerMachine.Application.Extensions;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddApplication(this IServiceCollection collection)
+ {
+ collection.AddScoped();
+ collection.AddScoped();
+
+ return collection;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/ATM.Infrastructure.DataAccess.csproj b/src/Lab5/ATM.Infrastructure.DataAccess/ATM.Infrastructure.DataAccess.csproj
new file mode 100644
index 0000000..a4cbf8e
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/ATM.Infrastructure.DataAccess.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/Extensions/ServiceCollectionExtensions.cs b/src/Lab5/ATM.Infrastructure.DataAccess/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..9a2dd4f
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,24 @@
+using ATM.Infrastructure.DataAccess.Repositories;
+using AutomatedTellerMachine.Abstractions.Repositories;
+using Itmo.Dev.Platform.Postgres.Extensions;
+using Itmo.Dev.Platform.Postgres.Models;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace ATM.Infrastructure.DataAccess.Extensions;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddInfrastructureDataAccess(
+ this IServiceCollection collection,
+ Action configuration)
+ {
+ collection.AddPlatformPostgres(builder => builder.Configure(configuration));
+ collection.AddPlatformMigrations(typeof(ServiceCollectionExtensions).Assembly);
+
+ collection.AddScoped();
+ collection.AddScoped();
+ collection.AddScoped();
+
+ return collection;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/Extensions/ServiceScopeExtensions.cs b/src/Lab5/ATM.Infrastructure.DataAccess/Extensions/ServiceScopeExtensions.cs
new file mode 100644
index 0000000..696bbbd
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/Extensions/ServiceScopeExtensions.cs
@@ -0,0 +1,12 @@
+using Itmo.Dev.Platform.Postgres.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace ATM.Infrastructure.DataAccess.Extensions;
+
+public static class ServiceScopeExtensions
+{
+ public static void UseInfrastructureDataAccess(this IServiceScope scope)
+ {
+ scope.UsePlatformMigrationsAsync(default).GetAwaiter().GetResult();
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/Migrations/01_Initial.cs b/src/Lab5/ATM.Infrastructure.DataAccess/Migrations/01_Initial.cs
new file mode 100644
index 0000000..b1797ea
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/Migrations/01_Initial.cs
@@ -0,0 +1,36 @@
+using FluentMigrator;
+using Itmo.Dev.Platform.Postgres.Migrations;
+
+namespace ATM.Infrastructure.DataAccess.Migrations;
+
+[Migration(1, "Initial")]
+public class Initial : SqlMigration
+{
+protected override string GetUpSql(IServiceProvider serviceProvider) =>
+ """
+ create table passwords
+ (
+ account_id bigint primary key ,
+ hash char(64) not null
+ );
+
+ create table accounts
+ (
+ account_id bigint primary key generated always as identity,
+ balance bigint not null
+ );
+
+ create table operations
+ (
+ account_id bigint primary key,
+ amount double precision not null
+ );
+ """;
+
+protected override string GetDownSql(IServiceProvider serviceProvider) =>
+ """
+ drop table accounts;
+ drop table operations;
+ drop table passwords;
+ """;
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/AccountRepository.cs b/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/AccountRepository.cs
new file mode 100644
index 0000000..4a137d5
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/AccountRepository.cs
@@ -0,0 +1,113 @@
+using AutomatedTellerMachine.Abstractions.Repositories;
+using AutomatedTellerMachine.Models;
+using Itmo.Dev.Platform.Postgres.Connection;
+using Npgsql;
+
+namespace ATM.Infrastructure.DataAccess.Repositories;
+
+public class AccountRepository : IAccountRepository
+{
+ private readonly IPostgresConnectionProvider _connectionProvider;
+
+ public AccountRepository(IPostgresConnectionProvider connectionProvider)
+ {
+ _connectionProvider = connectionProvider;
+ }
+
+ public double GetBalanceByAccountId(long id)
+ {
+ const string sql = @"
+ SELECT balance FROM accounts WHERE account_id = @AccountId";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ using var command = new NpgsqlCommand(sql, connection);
+ command.Parameters.AddWithValue("@AccountId", id);
+
+ object? result = command.ExecuteScalar();
+
+ return result != null ? (double)result : 0;
+ }
+
+ public void Debit(long id, double amount)
+ {
+ const string sql = @"
+ UPDATE accounts SET balance = balance + @Amount WHERE account_id = @AccountId";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ using var command = new NpgsqlCommand(sql, connection);
+ command.Parameters.AddWithValue("@AccountId", id);
+ command.Parameters.AddWithValue("@Amount", amount);
+
+ command.ExecuteScalar();
+ }
+
+ public void Credit(long id, double amount)
+ {
+ const string sql = @"
+ UPDATE accounts SET balance = balance - @Amount WHERE account_id = @AccountId";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ using var command = new NpgsqlCommand(sql, connection);
+ command.Parameters.AddWithValue("@AccountId", id);
+ command.Parameters.AddWithValue("@Amount", amount);
+
+ command.ExecuteScalar();
+ }
+
+ public Account CreateAccount(string hashedPin)
+ {
+ const string accountSql = @"
+ INSERT INTO accounts (balance)
+ VALUES (@Balance)
+ RETURNING account_id;";
+
+ const string passwordSql = @"
+ INSERT INTO passwords (account_id, hash)
+ VALUES (@AccountId, @Hash);";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ using NpgsqlTransaction transaction = connection.BeginTransaction();
+
+ try
+ {
+ // Insert into accounts table
+ using (var accountCommand = new NpgsqlCommand(accountSql, connection, transaction))
+ {
+ accountCommand.Parameters.AddWithValue("@Balance", 0);
+ long accountId = (long)(accountCommand.ExecuteScalar() ?? 0);
+
+ // Insert into passwords table
+ using (var passwordCommand = new NpgsqlCommand(passwordSql, connection, transaction))
+ {
+ passwordCommand.Parameters.AddWithValue("@AccountId", accountId);
+ passwordCommand.Parameters.AddWithValue("@Hash", hashedPin);
+ passwordCommand.ExecuteNonQuery();
+ }
+
+ transaction.Commit();
+ return new Account(accountId);
+ }
+ }
+ catch (Exception)
+ {
+ transaction.Rollback();
+ throw; // Re-throw the exception to be handled by the caller
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/OperationsRepository.cs b/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/OperationsRepository.cs
new file mode 100644
index 0000000..c3ea911
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/OperationsRepository.cs
@@ -0,0 +1,64 @@
+using AutomatedTellerMachine.Abstractions.Repositories;
+using AutomatedTellerMachine.Models;
+using Itmo.Dev.Platform.Postgres.Connection;
+using Npgsql;
+
+namespace ATM.Infrastructure.DataAccess.Repositories;
+
+public class OperationsRepository : IOperationsRepository
+{
+ private readonly IPostgresConnectionProvider _connectionProvider;
+
+ public OperationsRepository(IPostgresConnectionProvider connectionProvider)
+ {
+ _connectionProvider = connectionProvider;
+ }
+
+ public IEnumerable GetOperationsByAccount(long id)
+ {
+ const string sql = @"SELECT * FROM operations WHERE account_id = @AccountId";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ var operations = new List();
+
+ using (var command = new NpgsqlCommand(sql, connection))
+ {
+ command.Parameters.AddWithValue("@AccountId", id);
+
+ using (NpgsqlDataReader reader = command.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ // Assuming 'Operation' has properties corresponding to your table columns
+ var operation = new Operation((long)reader["account_id"], (double)reader["amount"]);
+
+ operations.Add(operation);
+ }
+ }
+ }
+
+ return operations;
+ }
+
+ public void AddOperation(long id, double amount)
+ {
+ const string sql = @"INSERT INTO operations (account_id, amount)
+ VALUES (@AccountID, @Amount)
+ ";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ using var command = new NpgsqlCommand(sql, connection);
+ command.Parameters.AddWithValue("@AccountId", id);
+ command.Parameters.AddWithValue("@Amount", amount);
+
+ command.ExecuteScalar();
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/PinRepository.cs b/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/PinRepository.cs
new file mode 100644
index 0000000..533a4be
--- /dev/null
+++ b/src/Lab5/ATM.Infrastructure.DataAccess/Repositories/PinRepository.cs
@@ -0,0 +1,34 @@
+using AutomatedTellerMachine.Abstractions.Repositories;
+using Itmo.Dev.Platform.Postgres.Connection;
+using Npgsql;
+
+namespace ATM.Infrastructure.DataAccess.Repositories;
+
+public class PinRepository : IPinRepository
+{
+ private readonly IPostgresConnectionProvider _connectionProvider;
+ public PinRepository(IPostgresConnectionProvider connectionProvider)
+ {
+ _connectionProvider = connectionProvider;
+ }
+
+ public string GetPinHashByAccountId(long id)
+ {
+ const string sql = @"
+ SELECT hash
+ FROM passwords
+ WHERE account_id = @UserId;";
+
+ NpgsqlConnection connection = _connectionProvider
+ .GetConnectionAsync(default)
+ .GetAwaiter()
+ .GetResult();
+
+ using var command = new NpgsqlCommand(sql, connection);
+ command.Parameters.AddWithValue("@UserId", id);
+
+ object? result = command.ExecuteScalar();
+
+ return result != null ? ((string)result).Trim() : string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/ATM.Presentation.Console.csproj b/src/Lab5/ATM.Presentation.Console/ATM.Presentation.Console.csproj
new file mode 100644
index 0000000..07ff5e0
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/ATM.Presentation.Console.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab5/ATM.Presentation.Console/CurrentAccount/CurrentAccount.cs b/src/Lab5/ATM.Presentation.Console/CurrentAccount/CurrentAccount.cs
new file mode 100644
index 0000000..28ef7ee
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/CurrentAccount/CurrentAccount.cs
@@ -0,0 +1,5 @@
+using AutomatedTellerMachine.Models;
+
+namespace ATM.Presentation.Console.CurrentAccount;
+
+public record CurrentAccount(long Id, string Token) : Account(Id);
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/CurrentAccount/CurrentAccountService.cs b/src/Lab5/ATM.Presentation.Console/CurrentAccount/CurrentAccountService.cs
new file mode 100644
index 0000000..c59b70f
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/CurrentAccount/CurrentAccountService.cs
@@ -0,0 +1,6 @@
+namespace ATM.Presentation.Console.CurrentAccount;
+
+public class CurrentAccountService
+{
+ public CurrentAccount? CurrentAccount { get; set; }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Extensions/ServiceCollectionExtensions.cs b/src/Lab5/ATM.Presentation.Console/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..253f9fa
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,24 @@
+using ATM.Presentation.Console.CurrentAccount;
+using ATM.Presentation.Console.Scenarios.Admin;
+using ATM.Presentation.Console.Scenarios.Login;
+using ATM.Presentation.Console.Scenarios.Menu;
+using ATM.Presentation.Console.Scenarios.Register;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace ATM.Presentation.Console.Extensions;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddPresentationConsole(this IServiceCollection collection)
+ {
+ collection.AddScoped();
+
+ collection.AddScoped();
+ collection.AddScoped();
+ collection.AddScoped();
+ collection.AddScoped();
+ collection.AddScoped();
+
+ return collection;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/IScenario.cs b/src/Lab5/ATM.Presentation.Console/IScenario.cs
new file mode 100644
index 0000000..d2d2deb
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/IScenario.cs
@@ -0,0 +1,10 @@
+namespace ATM.Presentation.Console;
+
+public interface IScenario
+{
+ string Name { get; }
+
+ void Run();
+
+ void SetNext(IScenario scenario);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/IScenarioProvider.cs b/src/Lab5/ATM.Presentation.Console/IScenarioProvider.cs
new file mode 100644
index 0000000..d2fb25c
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/IScenarioProvider.cs
@@ -0,0 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace ATM.Presentation.Console;
+
+public interface IScenarioProvider
+{
+ bool TryGetScenario([NotNullWhen(true)] out IScenario? scenario);
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/ScenarioRunner.cs b/src/Lab5/ATM.Presentation.Console/ScenarioRunner.cs
new file mode 100644
index 0000000..45fc204
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/ScenarioRunner.cs
@@ -0,0 +1,35 @@
+using Spectre.Console;
+
+namespace ATM.Presentation.Console;
+
+public class ScenarioRunner
+{
+ private readonly IEnumerable _providers;
+
+ public ScenarioRunner(IEnumerable providers)
+ {
+ _providers = providers;
+ }
+
+ public void Run()
+ {
+ IEnumerable scenarios = GetScenarios();
+
+ SelectionPrompt selector = new SelectionPrompt()
+ .Title("Select action")
+ .AddChoices(scenarios)
+ .UseConverter(x => x.Name);
+
+ IScenario scenario = AnsiConsole.Prompt(selector);
+ scenario.Run();
+ }
+
+ private IEnumerable GetScenarios()
+ {
+ foreach (IScenarioProvider provider in _providers)
+ {
+ if (provider.TryGetScenario(out IScenario? scenario))
+ yield return scenario;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/AdminScenarioProvider.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/AdminScenarioProvider.cs
new file mode 100644
index 0000000..7f6ec9d
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/AdminScenarioProvider.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics.CodeAnalysis;
+using ATM.Presentation.Console.CurrentAccount;
+using AutomatedTellerMachine.Contracts.Accounts;
+
+namespace ATM.Presentation.Console.Scenarios.Admin;
+
+public class AdminScenarioProvider : IScenarioProvider
+{
+ private readonly IAccountService _service;
+ private readonly CurrentAccountService _currentAccountService;
+
+ public AdminScenarioProvider(
+ IAccountService service, CurrentAccountService currentAccountService)
+ {
+ _service = service;
+ _currentAccountService = currentAccountService;
+ }
+
+ public bool TryGetScenario(
+ [NotNullWhen(true)] out IScenario? scenario)
+ {
+ if (_currentAccountService.CurrentAccount == null)
+ {
+ // scenario = new AdminScenario(_service, _currentAccountService.CurrentAccount);
+ // return true;
+ }
+
+ scenario = null;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/Chain/AdminMenuScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/Chain/AdminMenuScenario.cs
new file mode 100644
index 0000000..1f33a7f
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/Chain/AdminMenuScenario.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using AutomatedTellerMachine.Contracts.Accounts;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Admin.Chain;
+
+public class AdminMenuScenario : IScenario
+{
+ private readonly List _scenarios = new();
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccount.CurrentAccount _current;
+
+ public AdminMenuScenario(IAccountService accountService, CurrentAccount.CurrentAccount current)
+ {
+ _accountService = accountService;
+ _current = current;
+ }
+
+ public string Name => "Menu";
+
+ public void Run()
+ {
+ var sb = new StringBuilder();
+ sb.Append("Current Balance: ");
+ sb.Append(_accountService.GetBalance(_current.Id, _current.Token));
+
+ SelectionPrompt selector = new SelectionPrompt()
+ .Title(sb.ToString())
+ .AddChoices(_scenarios)
+ .UseConverter(x => x.Name);
+
+ IScenario scenario = AnsiConsole.Prompt(selector);
+ scenario.Run();
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ _scenarios.Add(scenario);
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/Chain/AdminScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/Chain/AdminScenario.cs
new file mode 100644
index 0000000..0e93356
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Admin/Chain/AdminScenario.cs
@@ -0,0 +1,39 @@
+using ATM.Presentation.Console.CurrentAccount;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Admin.Chain;
+
+public class AdminScenario : IScenario
+{
+ private readonly CurrentAccountService _currentAccountService;
+
+ public AdminScenario(CurrentAccountService currentAccountService)
+ {
+ _currentAccountService = currentAccountService;
+ }
+
+ public string Name => "Admin";
+
+ public void Run()
+ {
+ string password = AnsiConsole.Ask("Enter admin password: ");
+
+ // string hashedPass = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
+
+ // LoginResult result = _adminService.AdminLogin(hashedPass);
+ // switch (result)
+ // {
+ // case LoginResult.Success success:
+ // _currentAccountService.CurrentAccount = new CurrentAccount.CurrentAccount(0, success.Token);
+ // break;
+ // case LoginResult.NotFound:
+ // break;
+ // }
+ AnsiConsole.WriteLine(password);
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Login/LoginScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Login/LoginScenario.cs
new file mode 100644
index 0000000..433d68b
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Login/LoginScenario.cs
@@ -0,0 +1,49 @@
+using System.Security.Cryptography;
+using System.Text;
+using ATM.Presentation.Console.CurrentAccount;
+using AutomatedTellerMachine.Contracts.Accounts;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Login;
+
+public class LoginScenario : IScenario
+{
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccountService _currentAccountService;
+
+ public LoginScenario(IAccountService accountService, CurrentAccountService currentAccountService)
+ {
+ _accountService = accountService;
+ _currentAccountService = currentAccountService;
+ }
+
+ public string Name => "Login";
+
+ public void Run()
+ {
+ long account = AnsiConsole.Ask("Enter your account");
+ string pin = AnsiConsole.Ask("Enter your pin");
+
+ string hashedPin = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(pin)));
+
+ LoginResult result = _accountService.Login(account, hashedPin);
+
+ switch (result)
+ {
+ case LoginResult.Success success:
+ AnsiConsole.WriteLine("Login successful.");
+ _currentAccountService.CurrentAccount = new CurrentAccount.CurrentAccount(account, success.Token);
+ break;
+ case LoginResult.NotFound:
+ AnsiConsole.WriteLine("Account not found or incorrect password.");
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(result));
+ }
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Login/LoginScenarioProvider.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Login/LoginScenarioProvider.cs
new file mode 100644
index 0000000..20be725
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Login/LoginScenarioProvider.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics.CodeAnalysis;
+using ATM.Presentation.Console.CurrentAccount;
+using AutomatedTellerMachine.Contracts.Accounts;
+
+namespace ATM.Presentation.Console.Scenarios.Login;
+
+public class LoginScenarioProvider : IScenarioProvider
+{
+ private readonly IAccountService _service;
+ private readonly CurrentAccountService _currentAccountService;
+
+ public LoginScenarioProvider(
+ IAccountService service, CurrentAccountService currentAccountService)
+ {
+ _service = service;
+ _currentAccountService = currentAccountService;
+ }
+
+ public bool TryGetScenario(
+ [NotNullWhen(true)] out IScenario? scenario)
+ {
+ if (_currentAccountService.CurrentAccount == null)
+ {
+ scenario = new LoginScenario(_service, _currentAccountService);
+ return true;
+ }
+
+ scenario = null;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/CreditScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/CreditScenario.cs
new file mode 100644
index 0000000..3ba926f
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/CreditScenario.cs
@@ -0,0 +1,34 @@
+using AutomatedTellerMachine.Contracts.Accounts;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Menu.Chain;
+
+public class CreditScenario : IScenario
+{
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccount.CurrentAccount _current;
+ private IScenario? _nextScenario;
+
+ public CreditScenario(IAccountService accountService, CurrentAccount.CurrentAccount current)
+ {
+ _accountService = accountService;
+ _current = current;
+ }
+
+ public string Name => "Credit";
+
+ public void Run()
+ {
+ double amount = AnsiConsole.Ask("How much would you like to credit?\n");
+
+ _accountService.Credit(amount, _current.Id, _current.Token);
+
+ AnsiConsole.Clear();
+ _nextScenario?.Run();
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ _nextScenario = scenario;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/DebitScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/DebitScenario.cs
new file mode 100644
index 0000000..b12b1c6
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/DebitScenario.cs
@@ -0,0 +1,34 @@
+using AutomatedTellerMachine.Contracts.Accounts;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Menu.Chain;
+
+public class DebitScenario : IScenario
+{
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccount.CurrentAccount _current;
+ private IScenario? _nextScenario;
+
+ public DebitScenario(IAccountService accountService, CurrentAccount.CurrentAccount current)
+ {
+ _accountService = accountService;
+ _current = current;
+ }
+
+ public string Name => "Debit";
+
+ public void Run()
+ {
+ double amount = AnsiConsole.Ask("How much would you like to credit?\n");
+
+ _accountService.Debit(amount, _current.Id, _current.Token);
+
+ AnsiConsole.Clear();
+ _nextScenario?.Run();
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ _nextScenario = scenario;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/LogoutScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/LogoutScenario.cs
new file mode 100644
index 0000000..3dae929
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/LogoutScenario.cs
@@ -0,0 +1,27 @@
+using ATM.Presentation.Console.CurrentAccount;
+
+namespace ATM.Presentation.Console.Scenarios.Menu.Chain;
+
+public class LogoutScenario : IScenario
+{
+ private readonly CurrentAccountService _currentAccountService;
+ private IScenario? _nextScenario;
+
+ public LogoutScenario(CurrentAccountService currentAccountService)
+ {
+ _currentAccountService = currentAccountService;
+ }
+
+ public string Name => "Logout";
+
+ public void Run()
+ {
+ _currentAccountService.CurrentAccount = null;
+ _nextScenario?.Run();
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ _nextScenario = scenario;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/MenuScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/MenuScenario.cs
new file mode 100644
index 0000000..ed9798d
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/MenuScenario.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using AutomatedTellerMachine.Contracts.Accounts;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Menu.Chain;
+
+public class MenuScenario : IScenario
+{
+ private readonly List _scenarios = new();
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccount.CurrentAccount _current;
+
+ public MenuScenario(IAccountService accountService, CurrentAccount.CurrentAccount current)
+ {
+ _accountService = accountService;
+ _current = current;
+ }
+
+ public string Name => "Menu";
+
+ public void Run()
+ {
+ var sb = new StringBuilder();
+ sb.Append("Current Balance: ");
+ sb.Append(_accountService.GetBalance(_current.Id, _current.Token));
+
+ SelectionPrompt selector = new SelectionPrompt()
+ .Title(sb.ToString())
+ .AddChoices(_scenarios)
+ .UseConverter(x => x.Name);
+
+ IScenario scenario = AnsiConsole.Prompt(selector);
+ scenario.Run();
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ _scenarios.Add(scenario);
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/RecentOperationsScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/RecentOperationsScenario.cs
new file mode 100644
index 0000000..1c06b57
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/Chain/RecentOperationsScenario.cs
@@ -0,0 +1,47 @@
+using System.Globalization;
+using System.Text;
+using AutomatedTellerMachine.Contracts.Accounts;
+using AutomatedTellerMachine.Models;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Menu.Chain;
+
+public class RecentOperationsScenario : IScenario
+{
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccount.CurrentAccount _current;
+ private IScenario? _nextScenario;
+
+ public RecentOperationsScenario(IAccountService accountService, CurrentAccount.CurrentAccount current)
+ {
+ _accountService = accountService;
+ _current = current;
+ }
+
+ public string Name => "Recent Operations";
+
+ public void Run()
+ {
+ IEnumerable operations = _accountService.GetOperations(_current.Id, _current.Token);
+
+ AnsiConsole.WriteLine("Recent Operations:");
+
+ var sb = new StringBuilder();
+
+ foreach (Operation operation in operations)
+ {
+ sb.AppendLine(operation.Amount.ToString(CultureInfo.CurrentCulture));
+ }
+
+ AnsiConsole.WriteLine(sb.ToString());
+
+ AnsiConsole.Console.Input.ReadKey(false);
+ AnsiConsole.Clear();
+ _nextScenario?.Run();
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ _nextScenario = scenario;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/MenuScenarioProvider.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/MenuScenarioProvider.cs
new file mode 100644
index 0000000..defe4f8
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Menu/MenuScenarioProvider.cs
@@ -0,0 +1,55 @@
+using System.Diagnostics.CodeAnalysis;
+using ATM.Presentation.Console.CurrentAccount;
+using ATM.Presentation.Console.Scenarios.Menu.Chain;
+using AutomatedTellerMachine.Contracts.Accounts;
+
+namespace ATM.Presentation.Console.Scenarios.Menu;
+
+public class MenuScenarioProvider : IScenarioProvider
+{
+ private readonly IAccountService _service;
+ private readonly CurrentAccountService _currentAccountService;
+
+ public MenuScenarioProvider(
+ IAccountService service, CurrentAccountService currentAccountService)
+ {
+ _service = service;
+ _currentAccountService = currentAccountService;
+ }
+
+ public bool TryGetScenario(
+ [NotNullWhen(true)] out IScenario? scenario)
+ {
+ if (_currentAccountService.CurrentAccount != null)
+ {
+ scenario = GetScenarioChain(_currentAccountService.CurrentAccount);
+ return true;
+ }
+
+ scenario = null;
+ return false;
+ }
+
+ private IScenario GetScenarioChain(CurrentAccount.CurrentAccount account)
+ {
+ var entry = new MenuScenario(_service, account);
+
+ var credit = new CreditScenario(_service, account);
+ credit.SetNext(entry);
+
+ var debit = new DebitScenario(_service, account);
+ debit.SetNext(entry);
+
+ var operations = new RecentOperationsScenario(_service, account);
+ operations.SetNext(entry);
+
+ var logout = new LogoutScenario(_currentAccountService);
+
+ entry.SetNext(debit);
+ entry.SetNext(credit);
+ entry.SetNext(operations);
+ entry.SetNext(logout);
+
+ return entry;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Register/RegisterScenario.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Register/RegisterScenario.cs
new file mode 100644
index 0000000..4eff994
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Register/RegisterScenario.cs
@@ -0,0 +1,41 @@
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+using ATM.Presentation.Console.CurrentAccount;
+using AutomatedTellerMachine.Contracts.Accounts;
+using Spectre.Console;
+
+namespace ATM.Presentation.Console.Scenarios.Register;
+
+public class RegisterScenario : IScenario
+{
+ private readonly IAccountService _accountService;
+ private readonly CurrentAccountService _currentAccountService;
+
+ public RegisterScenario(IAccountService accountService, CurrentAccountService currentAccountService)
+ {
+ _accountService = accountService;
+ _currentAccountService = currentAccountService;
+ }
+
+ public string Name => "Register";
+
+ public void Run()
+ {
+ string pin1 = AnsiConsole.Ask("Enter your pin: ");
+ string pin2 = AnsiConsole.Ask("Re-enter your pin: ");
+
+ if (pin1 != pin2) Run();
+
+ string hashedPin = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(pin1)));
+
+ long id = _accountService.Create(hashedPin);
+
+ AnsiConsole.WriteLine(id.ToString(CultureInfo.CurrentCulture));
+ }
+
+ public void SetNext(IScenario scenario)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/ATM.Presentation.Console/Scenarios/Register/RegisterScenarioProvider.cs b/src/Lab5/ATM.Presentation.Console/Scenarios/Register/RegisterScenarioProvider.cs
new file mode 100644
index 0000000..80c8afc
--- /dev/null
+++ b/src/Lab5/ATM.Presentation.Console/Scenarios/Register/RegisterScenarioProvider.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics.CodeAnalysis;
+using ATM.Presentation.Console.CurrentAccount;
+using AutomatedTellerMachine.Contracts.Accounts;
+
+namespace ATM.Presentation.Console.Scenarios.Register;
+
+public class RegisterScenarioProvider : IScenarioProvider
+{
+ private readonly IAccountService _service;
+ private readonly CurrentAccountService _currentAccountService;
+
+ public RegisterScenarioProvider(
+ IAccountService service, CurrentAccountService currentAccountService)
+ {
+ _service = service;
+ _currentAccountService = currentAccountService;
+ }
+
+ public bool TryGetScenario(
+ [NotNullWhen(true)] out IScenario? scenario)
+ {
+ if (_currentAccountService.CurrentAccount == null)
+ {
+ scenario = new RegisterScenario(_service, _currentAccountService);
+ return true;
+ }
+
+ scenario = null;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/AutomatedTellerMachine.Ports/AutomatedTellerMachine.Ports.csproj b/src/Lab5/AutomatedTellerMachine.Ports/AutomatedTellerMachine.Ports.csproj
new file mode 100644
index 0000000..0542335
--- /dev/null
+++ b/src/Lab5/AutomatedTellerMachine.Ports/AutomatedTellerMachine.Ports.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
diff --git a/src/Lab5/AutomatedTellerMachine/AutomatedTellerMachine.csproj b/src/Lab5/AutomatedTellerMachine/AutomatedTellerMachine.csproj
new file mode 100644
index 0000000..ffcffe2
--- /dev/null
+++ b/src/Lab5/AutomatedTellerMachine/AutomatedTellerMachine.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+ Lab5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab5/AutomatedTellerMachine/Program.cs b/src/Lab5/AutomatedTellerMachine/Program.cs
new file mode 100644
index 0000000..e94a2c5
--- /dev/null
+++ b/src/Lab5/AutomatedTellerMachine/Program.cs
@@ -0,0 +1,37 @@
+// See https://aka.ms/new-console-template for more information
+
+using ATM.Infrastructure.DataAccess.Extensions;
+using ATM.Presentation.Console;
+using ATM.Presentation.Console.Extensions;
+using AutomatedTellerMachine.Application.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+using Spectre.Console;
+
+var collection = new ServiceCollection();
+
+collection
+ .AddApplication()
+ .AddInfrastructureDataAccess(configuration =>
+ {
+ configuration.Host = "localhost";
+ configuration.Port = 6432;
+ configuration.Username = "postgres";
+ configuration.Password = "postgres";
+ configuration.Database = "postgres";
+ configuration.SslMode = "Prefer";
+ })
+ .AddPresentationConsole();
+
+ServiceProvider provider = collection.BuildServiceProvider();
+using IServiceScope scope = provider.CreateScope();
+
+scope.UseInfrastructureDataAccess();
+
+ScenarioRunner scenarioRunner = scope.ServiceProvider
+ .GetRequiredService();
+
+while (true)
+{
+ scenarioRunner.Run();
+ AnsiConsole.Clear();
+}
\ No newline at end of file
diff --git a/src/Lab5/CommandLine/Class1.cs b/src/Lab5/CommandLine/Class1.cs
new file mode 100644
index 0000000..89ccea6
--- /dev/null
+++ b/src/Lab5/CommandLine/Class1.cs
@@ -0,0 +1,6 @@
+namespace CommandLine
+{
+ public class Class1
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/CommandLine/CommandLine.csproj b/src/Lab5/CommandLine/CommandLine.csproj
new file mode 100644
index 0000000..2ad9d3a
--- /dev/null
+++ b/src/Lab5/CommandLine/CommandLine.csproj
@@ -0,0 +1,54 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {61B79AE5-7777-4388-AC9B-A0AEB56D105F}
+ Library
+ Properties
+ CommandLine
+ CommandLine
+ v4.8
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab5/CommandLine/Properties/AssemblyInfo.cs b/src/Lab5/CommandLine/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..dd9be74
--- /dev/null
+++ b/src/Lab5/CommandLine/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("CommandLine")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("CommandLine")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("61B79AE5-7777-4388-AC9B-A0AEB56D105F")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/src/Lab5/PostgreSql/IDatabase.cs b/src/Lab5/PostgreSql/IDatabase.cs
new file mode 100644
index 0000000..f353d48
--- /dev/null
+++ b/src/Lab5/PostgreSql/IDatabase.cs
@@ -0,0 +1,7 @@
+namespace PostgreSql
+{
+ public interface IDatabase
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Lab5/PostgreSql/PostgreSql.csproj b/src/Lab5/PostgreSql/PostgreSql.csproj
new file mode 100644
index 0000000..9d10410
--- /dev/null
+++ b/src/Lab5/PostgreSql/PostgreSql.csproj
@@ -0,0 +1,54 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {896B50C3-CEF3-461E-89C4-2499DBD8F6A4}
+ Library
+ Properties
+ PostgreSql
+ PostgreSql
+ v4.8
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab5/PostgreSql/Properties/AssemblyInfo.cs b/src/Lab5/PostgreSql/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..740bb2e
--- /dev/null
+++ b/src/Lab5/PostgreSql/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PostgreSql")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PostgreSql")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("896B50C3-CEF3-461E-89C4-2499DBD8F6A4")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/src/Lab5/docker-compose.yaml b/src/Lab5/docker-compose.yaml
new file mode 100644
index 0000000..d2e1e9b
--- /dev/null
+++ b/src/Lab5/docker-compose.yaml
@@ -0,0 +1,12 @@
+version: '3.9'
+
+services:
+ postgres:
+ image: postgres:latest
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=postgres
+ - POSTGRES_DB=postgres
+ ports:
+ - "6432:5432"
+ restart: unless-stopped
\ No newline at end of file