├── ReadMe.md ├── images ├── architecture.png ├── cron-schedule.PNG ├── solution-design.png ├── statement-result.png ├── object-interaction.PNG ├── solution-structure.png ├── object-interaction-small.JPG ├── object-interaction-updated.png ├── asynchronous-communication-updated.PNG └── synchronous-communication-updated.PNG ├── src ├── Services │ ├── Identity │ │ ├── Helpers │ │ │ ├── AppSettings.cs │ │ │ └── AppException.cs │ │ ├── Models │ │ │ ├── SecurityToken.cs │ │ │ ├── Login.cs │ │ │ ├── UserAccount.cs │ │ │ └── User.cs │ │ ├── appsettings.json │ │ ├── appsettings.Development.json │ │ ├── Program.cs │ │ ├── Identity.WebApi.csproj │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Controllers │ │ │ ├── UserController.cs │ │ │ └── InternalController.cs │ │ ├── Startup.cs │ │ └── Services │ │ │ └── UserService.cs │ ├── Transaction │ │ ├── appsettings.Development.json │ │ ├── Services │ │ │ ├── IIdentityService.cs │ │ │ └── IdentityService.cs │ │ ├── appsettings.json │ │ ├── Models │ │ │ ├── TransactionModel.cs │ │ │ ├── IdentityModel.cs │ │ │ ├── TransactionResultModel.cs │ │ │ └── StatementResultModel.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Transaction.WebApi.csproj │ │ ├── Mappers │ │ │ ├── SetIdentityAction.cs │ │ │ └── ModelMappingProfile.cs │ │ ├── Controllers │ │ │ ├── InternalController.cs │ │ │ └── AccountController.cs │ │ ├── Startup.cs │ │ └── Middlewares │ │ │ └── ExceptionHandlerMiddleware.cs │ └── Statement │ │ └── Statement.WebApi │ │ ├── appsettings.Development.json │ │ ├── Models │ │ ├── ResultModel.cs │ │ └── IdentityModel.cs │ │ ├── Services │ │ ├── IIdentityService.cs │ │ └── IdentityService.cs │ │ ├── appsettings.json │ │ ├── Statement.WebApi.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── Controllers │ │ └── StatementController.cs │ │ ├── Startup.cs │ │ └── Middlewares │ │ └── ExceptionHandlerMiddleware.cs ├── Frameworks │ ├── Transaction │ │ ├── Types │ │ │ ├── TransactionType.cs │ │ │ ├── LoggingEvents.cs │ │ │ ├── StringResources.cs │ │ │ ├── TransactionErrorCode.cs │ │ │ ├── Currency.cs │ │ │ ├── StatementDate.cs │ │ │ ├── Guard.cs │ │ │ └── Money.cs │ │ ├── Domain │ │ │ ├── AccountSummary.cs │ │ │ ├── TransactionResult.cs │ │ │ ├── AccountTransaction.cs │ │ │ └── AccountStatement.cs │ │ ├── Data │ │ │ ├── Interface │ │ │ │ ├── IAccountSummaryRepository.cs │ │ │ │ └── IAccountTransactionRepository.cs │ │ │ ├── Entities │ │ │ │ ├── AccountSummaryEntity.cs │ │ │ │ └── AccountTransactionEntity.cs │ │ │ ├── EntityConfigurations │ │ │ │ ├── AccountSummaryEntityConfiguration.cs │ │ │ │ └── AccountTransactionEntityConfiguration.cs │ │ │ ├── ApplicationDbContext.cs │ │ │ └── Repositories │ │ │ │ ├── AccountSummaryRepository.cs │ │ │ │ └── AccountTransactionRepository.cs │ │ ├── Extensions │ │ │ ├── StringExtension.cs │ │ │ └── ServiceCollectionExtension.cs │ │ ├── Services │ │ │ ├── Interface │ │ │ │ └── ITransactionService.cs │ │ │ └── TransactionService.cs │ │ ├── Transaction.Framework.csproj │ │ ├── Validation │ │ │ └── TransactionValidation.cs │ │ ├── Exceptions │ │ │ └── TransactionException.cs │ │ └── Mappers │ │ │ └── MappingProfile.cs │ ├── Statement │ │ ├── Config.cs │ │ ├── Repositories │ │ │ ├── IDocumentRepository.cs │ │ │ ├── ICacheRepository.cs │ │ │ ├── DocumentRepository.cs │ │ │ └── CacheRepository.cs │ │ ├── Services │ │ │ ├── IStatementService.cs │ │ │ └── StatementService.cs │ │ ├── Statement.Framework.csproj │ │ ├── Exceptions │ │ │ └── StatementException.cs │ │ ├── Documents │ │ │ └── AccountStatement.cs │ │ └── Extensions │ │ │ └── ServiceCollectionExtensions.cs │ └── Publisher │ │ ├── Commands │ │ ├── ICommandPublisher.cs │ │ ├── ICommand.cs │ │ ├── CommandPublisher.cs │ │ └── Command.cs │ │ ├── Configurations │ │ ├── RabbitMqConfig.cs │ │ └── QueueNames.cs │ │ ├── Messages │ │ ├── BaseMessage.cs │ │ ├── TriggerMessage.cs │ │ └── StatementMessage.cs │ │ ├── Services │ │ ├── IPublishService.cs │ │ └── PublishService.cs │ │ ├── Publisher.Framework.csproj │ │ └── Extensions │ │ └── ServiceExtension.cs ├── Background │ ├── Receiver │ │ └── Receiver.Service │ │ │ ├── appsettings.Development.json │ │ │ ├── Models │ │ │ ├── UserAccount.cs │ │ │ └── StatementResultModel.cs │ │ │ ├── Processors │ │ │ ├── IMessageProcessor.cs │ │ │ ├── TriggerProcessor.cs │ │ │ ├── StatementProcessor.cs │ │ │ └── BaseProcessor.cs │ │ │ ├── Helpers │ │ │ ├── IRetryHelper.cs │ │ │ ├── RetryHelper.cs │ │ │ └── IHttpClientBase.cs │ │ │ ├── Clients │ │ │ ├── ITransactionClient.cs │ │ │ ├── IIdentityClient.cs │ │ │ ├── TransactionClient.cs │ │ │ └── IdentityClient.cs │ │ │ ├── Repository │ │ │ ├── ICacheRepository.cs │ │ │ ├── IDocumentRepository.cs │ │ │ ├── CacheRepository.cs │ │ │ └── DocumentRepository.cs │ │ │ ├── Types │ │ │ ├── StatementDate.cs │ │ │ └── Money.cs │ │ │ ├── Program.cs │ │ │ ├── Receiver.Service.csproj │ │ │ ├── appsettings.json │ │ │ ├── Properties │ │ │ └── launchSettings.json │ │ │ ├── Receivers │ │ │ ├── BaseReceiver.cs │ │ │ ├── ScopedReceiver.cs │ │ │ ├── TriggerReceiver.cs │ │ │ └── StatementReceiver.cs │ │ │ ├── Settings.cs │ │ │ ├── Documents │ │ │ └── AccountStatement.cs │ │ │ └── Startup.cs │ └── Publisher │ │ └── Publisher.Service │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Program.cs │ │ ├── Publisher.Service.csproj │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── Publishers │ │ ├── ScopedPublisher.cs │ │ ├── ScheduleTask.cs │ │ ├── BasePublisher.cs │ │ └── ScheduledPublisher.cs │ │ ├── Controllers │ │ └── PublishController.cs │ │ └── Startup.cs ├── Gateway │ └── Gateway.WebApi │ │ ├── appsettings.json │ │ ├── appsettings.Development.json │ │ ├── Program.cs │ │ ├── Gateway.WebApi.csproj │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── configuration.json │ │ └── Startup.cs ├── Database │ └── SimpleTransaction │ │ ├── dbo │ │ ├── AccountSummary.sql │ │ └── AccountTransaction.sql │ │ ├── Script.PostDeployment.sql │ │ └── SimpleTransaction.sqlproj └── App │ └── Console │ ├── SimpleBanking.ConsoleApp.csproj │ ├── Models.cs │ └── Extension │ └── TableParserExtension.cs ├── test └── Framework │ └── UnitTest │ ├── Transaction.Framework.UnitTest.csproj │ ├── Data │ └── Repositories │ │ ├── AccountSummaryRepositoryTest.cs │ │ └── AccountTransactionRepositoryTest.cs │ └── Services │ └── TransactionServiceTest.cs ├── .gitattributes └── .gitignore /ReadMe.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/ReadMe.md -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/architecture.png -------------------------------------------------------------------------------- /images/cron-schedule.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/cron-schedule.PNG -------------------------------------------------------------------------------- /images/solution-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/solution-design.png -------------------------------------------------------------------------------- /images/statement-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/statement-result.png -------------------------------------------------------------------------------- /images/object-interaction.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/object-interaction.PNG -------------------------------------------------------------------------------- /images/solution-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/solution-structure.png -------------------------------------------------------------------------------- /images/object-interaction-small.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/object-interaction-small.JPG -------------------------------------------------------------------------------- /images/object-interaction-updated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/object-interaction-updated.png -------------------------------------------------------------------------------- /images/asynchronous-communication-updated.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/asynchronous-communication-updated.PNG -------------------------------------------------------------------------------- /images/synchronous-communication-updated.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnph/simple-transaction-part2/HEAD/images/synchronous-communication-updated.PNG -------------------------------------------------------------------------------- /src/Services/Identity/Helpers/AppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Identity.WebApi.Helpers 2 | { 3 | public class AppSettings 4 | { 5 | public string Secret { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Services/Identity/Models/SecurityToken.cs: -------------------------------------------------------------------------------- 1 | namespace Identity.WebApi.Models 2 | { 3 | public class SecurityToken 4 | { 5 | public string auth_token { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/TransactionType.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | public enum TransactionType 4 | { 5 | Deposit = 1, 6 | Withdrawal = 2 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Services/Transaction/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Models/ResultModel.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi.Models 2 | { 3 | public class ResultModel 4 | { 5 | public bool IsSuccessful { get; set; } 6 | public string Message { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Services/Identity/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "Secret": "3ce1637ed40041cd94d4853d3e766c4d" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Warning" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /src/Services/Transaction/Services/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Services 2 | { 3 | using Transaction.WebApi.Models; 4 | 5 | public interface IIdentityService 6 | { 7 | IdentityModel GetIdentity(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "Secret": "3ce1637ed40041cd94d4853d3e766c4d" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Warning" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Services/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi.Services 2 | { 3 | using Statement.WebApi.Models; 4 | 5 | public interface IIdentityService 6 | { 7 | IdentityModel GetIdentity(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Statement.Framework 6 | { 7 | public class CacheExpiry 8 | { 9 | public int ExpiryInSeconds { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Database/SimpleTransaction/dbo/AccountSummary.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[AccountSummary]( 2 | [AccountNumber] [int] NOT NULL, 3 | [Balance] [decimal](19,2) NOT NULL, 4 | [Currency] [varchar](3) NOT NULL 5 | CONSTRAINT [PK_AccountSummary] PRIMARY KEY CLUSTERED([AccountNumber]) 6 | ); 7 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Commands/ICommandPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Framework.Commands 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public interface ICommandPublisher 6 | { 7 | Task PublishAsync(string queueName, T message) where T : Command; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/LoggingEvents.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | public class LoggingEvents 4 | { 5 | public const int Balance = 1000; 6 | public const int Deposit = 1001; 7 | public const int Withdrawal = 1002; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Models/UserAccount.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Models 2 | { 3 | public class UserAccount 4 | { 5 | public string Name { get; set; } 6 | public int AccountNumber { get; set; } 7 | public string Currency { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Domain/AccountSummary.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Domain 2 | { 3 | using Transaction.Framework.Types; 4 | 5 | public class AccountSummary 6 | { 7 | public int AccountNumber { get; set; } 8 | public Money Balance { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Services/Identity/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "Secret": "3ce1637ed40041cd94d4853d3e766c4d" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Debug", 8 | "System": "Information", 9 | "Microsoft": "Information" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Configurations/RabbitMqConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Publisher.Framework.Configurations 6 | { 7 | public class RabbitMQConfig 8 | { 9 | public string ConnectionString { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Repositories/IDocumentRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Repositories 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public interface IDocumentRepository where TDocument : class 6 | { 7 | Task GetAsync(TKey id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "Secret": "3ce1637ed40041cd94d4853d3e766c4d" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Debug", 8 | "System": "Information", 9 | "Microsoft": "Information" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Models/IdentityModel.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi.Models 2 | { 3 | public class IdentityModel 4 | { 5 | public int AccountNumber { get; set; } 6 | public string FullName { get; set; } 7 | public string Currency { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Processors/IMessageProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Processors 2 | { 3 | using Publisher.Framework.Commands; 4 | using System.Threading.Tasks; 5 | 6 | public interface IMessageProcessor 7 | { 8 | Task ProcessMessageAsync(ICommand command); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Services/IStatementService.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Services 2 | { 3 | using Statement.Framework.Documents; 4 | using System.Threading.Tasks; 5 | 6 | public interface IStatementService 7 | { 8 | Task GetAsync(int accountNumber, string month); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Helpers/IRetryHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Helpers 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | public interface IRetryHelper 7 | { 8 | Task Do(Func action, T message, TimeSpan retryInterval, int maxAttemptCount = 3); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Messages/BaseMessage.cs: -------------------------------------------------------------------------------- 1 | using Publisher.Framework.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Publisher.Framework.Messages 7 | { 8 | public abstract class BaseMessage : Command 9 | { 10 | public string Month { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Clients/ITransactionClient.cs: -------------------------------------------------------------------------------- 1 | using Receiver.Service.Models; 2 | namespace Receiver.Service.Clients 3 | { 4 | using System.Threading.Tasks; 5 | 6 | public interface ITransactionClient 7 | { 8 | Task GetStatement(int accountNumber, string month); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Configurations/QueueNames.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Publisher.Framework.Configurations 6 | { 7 | public class QueueNames 8 | { 9 | public string Trigger { get; set; } 10 | public string Statement { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Services/Identity/Models/Login.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Identity.WebApi.Models 7 | { 8 | public class Login 9 | { 10 | public string Username { get; set; } 11 | public string Password { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/Interface/IAccountSummaryRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data.Interface 2 | { 3 | using System.Threading.Tasks; 4 | using Transaction.Framework.Data.Entities; 5 | 6 | public interface IAccountSummaryRepository 7 | { 8 | Task Read(int accountNumber); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Messages/TriggerMessage.cs: -------------------------------------------------------------------------------- 1 | using Publisher.Framework.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Publisher.Framework.Messages 7 | { 8 | public class TriggerMessage : BaseMessage 9 | { 10 | public IEnumerable AccountNumbers { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Services/IPublishService.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Framework.Services 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | public interface IPublishService 7 | { 8 | Task PublishAsync(string month); 9 | Task PublishAsync(string month, IEnumerable accountNumbers); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/App/Console/SimpleBanking.ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Services/Transaction/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "SqlServerConnection": "" 4 | }, 5 | "ApplicationInsights": { 6 | "InstrumentationKey": "" 7 | }, 8 | "Logging": { 9 | "LogLevel": { 10 | "Default": "Warning" 11 | } 12 | }, 13 | "AllowedHosts": "*" 14 | } 15 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Messages/StatementMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Publisher.Framework.Messages 6 | { 7 | public class StatementMessage : BaseMessage 8 | { 9 | public string Name { get; set; } 10 | public int AccountNumber { get; set; } 11 | public string Currency { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Framework.Commands 2 | { 3 | using System; 4 | 5 | public interface ICommand 6 | { 7 | Guid CorrelationId { get; set; } 8 | DateTime ReceivedOn { get; set; } 9 | } 10 | 11 | public interface ICommand : ICommand 12 | { 13 | object Payload { get; set; } 14 | T GetBody(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Services/Identity/Models/UserAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Identity.WebApi.Models 7 | { 8 | public class UserAccount 9 | { 10 | public string Name { get; set; } 11 | public int AccountNumber { get; set; } 12 | public string Currency { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Services/Transaction/Models/TransactionModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Transaction.Framework.Types; 3 | 4 | namespace Transaction.WebApi.Models 5 | { 6 | public class TransactionModel 7 | { 8 | public decimal Amount { get; set; } 9 | public string Currency { get; set; } 10 | public DateTime Date { get; set; } 11 | public string Description { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Domain/TransactionResult.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Domain 2 | { 3 | using Transaction.Framework.Types; 4 | 5 | public class TransactionResult 6 | { 7 | public int AccountNumber { get; set; } 8 | public bool IsSuccessful { get; set; } 9 | public Money Balance { get; set; } 10 | public string Message { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Services/Transaction/Models/IdentityModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Transaction.WebApi.Models 7 | { 8 | public class IdentityModel 9 | { 10 | public int AccountNumber { get; set; } 11 | public string FullName { get; set; } 12 | public string Currency { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Services/Transaction/Models/TransactionResultModel.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Models 2 | { 3 | public class TransactionResultModel 4 | { 5 | public int? AccountNumber { get; set; } 6 | public bool IsSuccessful { get; set; } 7 | public decimal? Balance { get; set; } 8 | public string Currency { get; set; } 9 | public string Message { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "RabbitMQ": { 8 | "ConnectionString": "host=localhost; port=5672;VirtualHost=V-Host.SimpleTransaction" 9 | }, 10 | "QueueNames": { 11 | "Trigger": "simpletransaction.trigger", 12 | "Statement": "simpletransaction.statement" 13 | }, 14 | "AllowedHosts": "*" 15 | } 16 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Repositories/ICacheRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Repositories 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | public interface ICacheRepository where TValue : class 7 | { 8 | Task KeyExistsAsync(string key); 9 | Task GetAsync(string key); 10 | Task SetAsync(string key, TValue value, TimeSpan? expiry); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Extensions/StringExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Extensions 2 | { 3 | using System; 4 | 5 | public static class StringExtension 6 | { 7 | public static TEnum TryParseEnum(this string item) where TEnum : struct 8 | { 9 | return Enum.TryParse(item, true, out TEnum tEnumResult) ? 10 | tEnumResult : default(TEnum); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/StringResources.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | public static class StringResources 4 | { 5 | public const string TransactionSuccessfull = "Transaction successfull"; 6 | public const string TransactionFailed = "Transaction failed"; 7 | public const string DepositDescription = "Credit"; 8 | public const string WithdrawDescription = "Debit"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "MongoConfiguration": { 8 | "ConnectionString": "mongodb://localhost:27017", 9 | "Database": "monthly-statement" 10 | }, 11 | "RedisCache": { 12 | "ConnectionString": "127.0.0.1:6379,connectTimeout=5000" 13 | }, 14 | "CacheExpiryInSeconds": 10000, 15 | "AllowedHosts": "*" 16 | } 17 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/TransactionErrorCode.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | public static class TransactionErrorCode 4 | { 5 | public const int AccountDoesNotExistError = 1001; 6 | public const int InsufficientBalance = 1002; 7 | public const int InvalidAmount = 1003; 8 | public const int InvalidCurrencyError = 1004; 9 | public const int CurrencyMismatchError = 1005; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Clients/IIdentityClient.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Clients 2 | { 3 | using Receiver.Service.Models; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | public interface IIdentityClient 8 | { 9 | Task> GetAccountNumbers(); 10 | Task> GetUserAccounts(); 11 | Task GetUserAccount(int accountnumber); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Services/Identity/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Identity.WebApi.Models 7 | { 8 | public class User 9 | { 10 | public int AccountNumber { get; set; } 11 | public string FullName { get; set; } 12 | public string Currency { get; set; } 13 | public string Username { get; set; } 14 | public string Password { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Repository/ICacheRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Receiver.Service.Repository 7 | { 8 | public interface ICacheRepository where TValue : class 9 | { 10 | Task KeyExistsAsync(string key); 11 | Task SetAsync(string key, TValue value, TimeSpan? expiry); 12 | Task KeyDeleteAsync(string key); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/Currency.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | using System.ComponentModel; 4 | 5 | public enum Currency 6 | { 7 | Unknown = 0, 8 | 9 | [Description("United States dollar")] 10 | USD = 840, 11 | 12 | [Description("Pound sterling")] 13 | GBP = 826, 14 | 15 | [Description("Euro")] 16 | EUR = 978, 17 | 18 | [Description("Indian rupee")] 19 | INR = 356 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Services/Identity/Helpers/AppException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Identity.WebApi.Helpers 5 | { 6 | public class AppException : Exception 7 | { 8 | public AppException() : base() { } 9 | 10 | public AppException(string message) : base(message) { } 11 | 12 | public AppException(string message, params object[] args) 13 | : base(String.Format(CultureInfo.CurrentCulture, message, args)) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Services/Identity/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace Identity.WebApi 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Gateway.WebApi 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Publisher.Framework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Publisher.Framework 6 | Publisher.Framework 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Domain/AccountTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Transaction.Framework.Types; 3 | 4 | namespace Transaction.Framework.Domain 5 | { 6 | public class AccountTransaction 7 | { 8 | public int AccountNumber { get; set; } 9 | public TransactionType TransactionType { get; set; } 10 | public DateTime Date { get; set; } 11 | public string Description { get; set; } 12 | public Money Amount { get; set; } 13 | public Money CurrentBalance { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/Gateway.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Services/Transaction/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .UseApplicationInsights(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Services/Identity/Identity.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/Interface/IAccountTransactionRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data.Interface 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using Transaction.Framework.Data.Entities; 7 | 8 | public interface IAccountTransactionRepository 9 | { 10 | Task Create(AccountTransactionEntity accountTransactionEntity, AccountSummaryEntity accountSummaryEntity); 11 | Task> Get(int accountNumber, DateTime startDate, DateTime endDate); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/Entities/AccountSummaryEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Transaction.Framework.Data.Entities 6 | { 7 | [Table("AccountSummary", Schema = "dbo")] 8 | public class AccountSummaryEntity 9 | { 10 | [Key] 11 | public int AccountNumber { get; set; } 12 | public decimal Balance { get; set; } 13 | public string Currency { get; set; } 14 | public ICollection AccountTransactions { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Services/Interface/ITransactionService.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Services.Interface 2 | { 3 | using System.Threading.Tasks; 4 | using Transaction.Framework.Domain; 5 | using Transaction.Framework.Types; 6 | 7 | public interface ITransactionService 8 | { 9 | Task Balance(int accountNumber); 10 | Task Deposit(AccountTransaction accountTransaction); 11 | Task Withdraw(AccountTransaction accountTransaction); 12 | Task Statement(int accountNumber, StatementDate statementDate); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Statement.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Statement.Framework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/StatementDate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Transaction.Framework.Types 4 | { 5 | public class StatementDate 6 | { 7 | public StatementDate(string dateString) 8 | { 9 | StartDate = Convert.ToDateTime(dateString); 10 | EndDate = StartDate.AddMonths(1).AddTicks(-1); 11 | } 12 | 13 | public DateTime StartDate { get; set; } 14 | public DateTime EndDate { get; set; } 15 | 16 | public static implicit operator StatementDate(string date) 17 | { 18 | return string.IsNullOrEmpty(date) ? null : new StatementDate(date); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Database/SimpleTransaction/dbo/AccountTransaction.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[AccountTransaction]( 2 | [TransactionId] [int] IDENTITY(1,1) NOT NULL, 3 | [AccountNumber] [int] NOT NULL, 4 | [Date] DATETIME2 CONSTRAINT [DF_AccountTransaction_Date] DEFAULT (getutcdate()) NOT NULL, 5 | [Description] [varchar](200) NOT NULL, 6 | [TransactionType] [varchar](10) NOT NULL, 7 | [Amount] [decimal](19,2) NOT NULL, 8 | [CurrentBalance] [decimal](19,2) NOT NULL 9 | CONSTRAINT [PK_AccountTransaction] PRIMARY KEY CLUSTERED ([TransactionId] ASC), 10 | CONSTRAINT [FK_AccountTransaction_AccountNumber] FOREIGN KEY ([AccountNumber]) REFERENCES [dbo].[AccountSummary] ([AccountNumber]) 11 | ); 12 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Types/StatementDate.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Types 2 | { 3 | using System; 4 | 5 | public class StatementDate 6 | { 7 | public StatementDate(string dateString) 8 | { 9 | StartDate = Convert.ToDateTime(dateString); 10 | EndDate = StartDate.AddMonths(1).AddTicks(-1); 11 | } 12 | 13 | public DateTime StartDate { get; set; } 14 | public DateTime EndDate { get; set; } 15 | 16 | public static implicit operator StatementDate(string date) 17 | { 18 | return string.IsNullOrEmpty(date) ? null : new StatementDate(date); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/EntityConfigurations/AccountSummaryEntityConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data.EntityConfigurations 2 | { 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | using Transaction.Framework.Data.Entities; 5 | 6 | public static class AccountSummaryEntityConfiguration 7 | { 8 | public static void Configure(EntityTypeBuilder entityBuilder) 9 | { 10 | entityBuilder.HasKey(t => t.AccountNumber); 11 | entityBuilder.Property(t => t.Balance).IsConcurrencyToken().IsRequired(); 12 | entityBuilder.Property(t => t.Currency).IsRequired(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Receiver.Service 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Statement.WebApi 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Publisher.Service 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Commands/CommandPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Framework.Commands 2 | { 3 | using EasyNetQ; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | public class CommandPublisher : ICommandPublisher 8 | { 9 | private readonly IBus _bus; 10 | 11 | public CommandPublisher(IBus bus) 12 | { 13 | _bus = bus ?? throw new ArgumentNullException(nameof(bus)); 14 | } 15 | 16 | public async Task PublishAsync(string queueName, T message) where T : Command 17 | { 18 | if (message == null) 19 | throw new ArgumentNullException(nameof(message)); 20 | 21 | await _bus.SendAsync(queueName, message); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Receiver.Service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Services/Transaction/Models/StatementResultModel.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Transaction.WebApi.Models 3 | { 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | public class StatementResultModel 8 | { 9 | public int AccountNumber { get; set; } 10 | public string Currency { get; set; } 11 | public string Date { get; set; } 12 | public IEnumerable TransactionDetails { get; set; } 13 | } 14 | 15 | public class StatementTransactionModel 16 | { 17 | public string TransactionType { get; set; } 18 | public DateTime Date { get; set; } 19 | public string Description { get; set; } 20 | public decimal Amount { get; set; } 21 | public decimal CurrentBalance { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Repositories/DocumentRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Repositories 2 | { 3 | using MongoDB.Driver; 4 | using System.Threading.Tasks; 5 | 6 | public class DocumentRepository : IDocumentRepository where TDocument : class 7 | { 8 | private IMongoCollection _collection; 9 | 10 | public DocumentRepository(IMongoCollection collection) 11 | { 12 | _collection = collection; 13 | } 14 | 15 | public async Task GetAsync(TKey id) 16 | { 17 | var filter = Builders.Filter.Eq("_id", id); 18 | var result = await _collection.Find(filter).FirstOrDefaultAsync(); 19 | 20 | return result; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/Entities/AccountTransactionEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Transaction.Framework.Data.Entities 6 | { 7 | [Table("AccountTransaction")] 8 | public class AccountTransactionEntity 9 | { 10 | [Key] public int TransactionId { get; set; } 11 | [ForeignKey("AccountNumber")] public int AccountNumber { get; set; } 12 | public DateTime Date { get; set; } 13 | public string Description { get; set; } 14 | public string TransactionType { get; set; } 15 | public decimal Amount { get; set; } 16 | public decimal CurrentBalance { get; set; } 17 | public AccountSummaryEntity AccountSummary { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Publisher.Service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | Publisher.Service 7 | Publisher.Service 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/Framework/UnitTest/Transaction.Framework.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "MongoConfiguration": { 9 | "ConnectionString": "mongodb://localhost:27017", 10 | "Database": "monthly-statement" 11 | }, 12 | "RabbitMQ": { 13 | "ConnectionString": "host=localhost; port=5672;VirtualHost=V-Host.SimpleTransaction" 14 | }, 15 | "BaseUrlSettings": { 16 | "IdentityApiBaseUrl": "http://localhost:54203", 17 | "TransactionApiBaseUrl": "http://localhost:60243" 18 | }, 19 | "RedisCache": { 20 | "ConnectionString": "127.0.0.1:6379,connectTimeout=5000" 21 | }, 22 | "CacheExpiryInSeconds": 10000, 23 | "QueueNames": { 24 | "Trigger": "simpletransaction.trigger", 25 | "Statement": "simpletransaction.statement" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Domain/AccountStatement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Transaction.Framework.Types; 5 | 6 | namespace Transaction.Framework.Domain 7 | { 8 | public class AccountStatement 9 | { 10 | public int AccountNumber { get; set; } 11 | public Currency Currency { get; set; } 12 | public StatementDate Date { get; set; } 13 | public IEnumerable TransactionDetails { get; set; } 14 | } 15 | 16 | public class StatementTransaction 17 | { 18 | public TransactionType TransactionType { get; set; } 19 | public DateTime Date { get; set; } 20 | public string Description { get; set; } 21 | public Money Amount { get; set; } 22 | public Money CurrentBalance { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Models/StatementResultModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Receiver.Service.Models 7 | { 8 | public class StatementResultModel 9 | { 10 | public int AccountNumber { get; set; } 11 | public string Currency { get; set; } 12 | public string Date { get; set; } 13 | public IEnumerable TransactionDetails { get; set; } 14 | } 15 | 16 | public class StatementTransactionModel 17 | { 18 | public string TransactionType { get; set; } 19 | public DateTime Date { get; set; } 20 | public string Description { get; set; } 21 | public decimal Amount { get; set; } 22 | public decimal CurrentBalance { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:55448", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Publisher.Service": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "applicationUrl": "http://localhost:55448", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:55238", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Receiver.Service": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "applicationUrl": "http://localhost:55238", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/Guard.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | using System; 4 | 5 | public static class Guard 6 | { 7 | public static void ArgumentNotNull(string argumentName, object value) 8 | { 9 | if (value == null) 10 | { 11 | throw new ArgumentNullException(argumentName); 12 | } 13 | } 14 | 15 | public static void ArgumentNotNullOrEmpty(string argumentName, string value) 16 | { 17 | if (value == null) 18 | { 19 | throw new ArgumentNullException(argumentName); 20 | } 21 | 22 | if (string.IsNullOrEmpty(value)) 23 | { 24 | throw new ArgumentException("Value cannot be an empty string.", argumentName); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Transaction.Framework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | John Prabhu Harris 6 | 1.1 7 | Transaction.Framework 8 | Transaction.Framework 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data 2 | { 3 | using Microsoft.EntityFrameworkCore; 4 | using Transaction.Framework.Data.Entities; 5 | using Transaction.Framework.Data.EntityConfigurations; 6 | 7 | public class ApplicationDbContext : DbContext 8 | { 9 | public ApplicationDbContext(DbContextOptions options) 10 | : base(options) 11 | { } 12 | 13 | protected override void OnModelCreating(ModelBuilder modelBuilder) 14 | { 15 | AccountSummaryEntityConfiguration 16 | .Configure(modelBuilder.Entity()); 17 | 18 | AccountTransactionEntityConfiguration 19 | .Configure(modelBuilder.Entity()); 20 | 21 | base.OnModelCreating(modelBuilder); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Services/Identity/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:54203", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Identity.WebApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "http://localhost:54203", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Services/Transaction/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:60243", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Transaction.WebApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "applicationUrl": "http://localhost:60243", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:54784", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Gateway.WebApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "http://localhost:54784", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:55321", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Statement.WebApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "http://localhost:55321", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Database/SimpleTransaction/Script.PostDeployment.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Post-Deployment Script Template 3 | -------------------------------------------------------------------------------------- 4 | This file contains SQL statements that will be appended to the build script. 5 | Use SQLCMD syntax to include a file in the post-deployment script. 6 | Example: :r .\myfile.sql 7 | Use SQLCMD syntax to reference a variable in the post-deployment script. 8 | Example: :setvar TableName MyTable 9 | SELECT * FROM [$(TableName)] 10 | -------------------------------------------------------------------------------------- 11 | */ 12 | Insert Into dbo.AccountSummary(AccountNumber, Balance, Currency) Values(3628101, 25000, 'EUR') 13 | Insert Into dbo.AccountSummary(AccountNumber, Balance, Currency) Values(3637897, 75000, 'EUR') 14 | Insert Into dbo.AccountSummary(AccountNumber, Balance, Currency) Values(3648755, 117600, 'EUR') 15 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Exceptions/StatementException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Statement.Framework.Exceptions 6 | { 7 | public abstract class StatementException : Exception 8 | { 9 | public StatementException(string message) 10 | : base(message) 11 | { } 12 | 13 | public abstract int ErrorCode { get; } 14 | } 15 | 16 | public class StatementUnavailableException : StatementException 17 | { 18 | public StatementUnavailableException(int accountNumber, string month) 19 | : base($"The account statement for {accountNumber} and {month} is not available.") 20 | { } 21 | 22 | public override int ErrorCode => 23 | StatementErrorCode.StatementNotAvailable; 24 | } 25 | 26 | public static class StatementErrorCode 27 | { 28 | public const int StatementNotAvailable = 2001; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Publishers/ScopedPublisher.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace Publisher.Service.Publishers 7 | { 8 | public abstract class ScopedPublisher : BasePublisher 9 | { 10 | private readonly IServiceScopeFactory _serviceScopeFactory; 11 | 12 | public ScopedPublisher(IServiceScopeFactory serviceScopeFactory, ILogger logger) : base(logger) 13 | { 14 | _serviceScopeFactory = serviceScopeFactory; 15 | } 16 | 17 | protected override async Task Process() 18 | { 19 | using (var scope = _serviceScopeFactory.CreateScope()) 20 | { 21 | await ProcessInScope(scope.ServiceProvider); 22 | } 23 | } 24 | 25 | public abstract Task ProcessInScope(IServiceProvider serviceProvider); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Helpers/RetryHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Helpers 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | public class RetryHelper : IRetryHelper 8 | { 9 | public async Task Do(Func action, T message, TimeSpan retryInterval, int maxAttemptCount = 3) 10 | { 11 | for (int attempted = 0; attempted < maxAttemptCount; attempted++) 12 | { 13 | try 14 | { 15 | if (attempted > 0) 16 | { 17 | Thread.Sleep(retryInterval); 18 | } 19 | 20 | await action(message).ConfigureAwait(false); 21 | } 22 | catch (Exception ex) 23 | { 24 | if (attempted >= maxAttemptCount) 25 | throw ex; 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Helpers/IHttpClientBase.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Helpers 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public interface IHttpClientBase 6 | { 7 | Task GetAsync(string requestUri); 8 | Task PostJsonAsync(string requestUri, TValue value); 9 | Task PutJsonAsync(string requestUri, TValue value); 10 | Task PostAsync(string requestUri, TValue value); 11 | Task PutAsync(string requestUri, TValue value); 12 | Task PostJsonAsync(string requestUri, TValue value); 13 | Task PutJsonAsync(string requestUri, TValue value); 14 | Task PostAsync(string requestUri, TValue value); 15 | Task PutAsync(string requestUri, TValue value); 16 | Task DeleteAsync(string requestUri); 17 | Task DeleteAsync(string requestUri); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Services/Transaction/Transaction.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | John Prabhu Harris 7 | Simple Transaction Processing 8 | Transaction.WebApi 9 | Transaction.WebApi 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Services/Identity/Controllers/UserController.cs: -------------------------------------------------------------------------------- 1 | namespace Identity.WebApi.Controllers 2 | { 3 | using Identity.WebApi.Models; 4 | using Identity.WebApi.Services; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | [Route("api/user")] 9 | [ApiController] 10 | public class UserController : ControllerBase 11 | { 12 | private IUserService _userService; 13 | 14 | public UserController(IUserService userService) 15 | { 16 | _userService = userService; 17 | } 18 | 19 | [AllowAnonymous] 20 | [HttpPost("authenticate")] 21 | public IActionResult Authenticate([FromBody] Login loginParam) 22 | { 23 | var token = _userService.Authenticate(loginParam.Username, loginParam.Password); 24 | 25 | if (token == null) 26 | return BadRequest(new { message = "Username or password is incorrect" }); 27 | 28 | return Ok(token); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Clients/TransactionClient.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Clients 2 | { 3 | using Receiver.Service.Helpers; 4 | using Receiver.Service.Models; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | 8 | public class TransactionClient : HttpClientBase, ITransactionClient 9 | { 10 | public TransactionClient(IHttpClientFactory httpClientFactory) : base(httpClientFactory) { } 11 | 12 | public async Task GetStatement(int accountNumber, string month) 13 | { 14 | var relativeUri = $"api/internal/{accountNumber}/statement/{month}"; 15 | var result = await GetAsync(relativeUri); 16 | return result; 17 | } 18 | 19 | protected override HttpClient GetScopedHttpClient() 20 | { 21 | var httpClient = _httpClientFactory.CreateClient(NamedHttpClients.TransactionServiceClient); 22 | return httpClient; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/EntityConfigurations/AccountTransactionEntityConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data.EntityConfigurations 2 | { 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | using Transaction.Framework.Data.Entities; 5 | 6 | public static class AccountTransactionEntityConfiguration 7 | { 8 | public static void Configure(EntityTypeBuilder entityBuilder) 9 | { 10 | entityBuilder.HasKey(t => t.TransactionId); 11 | entityBuilder.HasOne(u => u.AccountSummary).WithMany(e => e.AccountTransactions).HasForeignKey(u => u.AccountNumber); 12 | entityBuilder.Property(t => t.Date).IsRequired(); 13 | entityBuilder.Property(t => t.Description).IsRequired(); 14 | entityBuilder.Property(t => t.TransactionType).IsRequired(); 15 | entityBuilder.Property(t => t.Amount).IsRequired(); 16 | entityBuilder.Property(t => t.CurrentBalance).IsRequired(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Repository/IDocumentRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Repository 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | public interface IDocumentRepository where TDocument : class 7 | { 8 | Task> GetAllAsync(); 9 | Task> GetAsync(string field, TValue value); 10 | Task> GetAsync(int startingFrom, int count); 11 | Task GetAsync(TKey id); 12 | Task InsertAsync(TDocument document); 13 | Task UpdateAsync(TKey id, string field, TValue value); 14 | Task UpdateAsync(TKey id, TDocument document); 15 | Task DeleteAsync(TKey id); 16 | Task DeleteAsync(string field, TValue value); 17 | Task DeleteAllAsync(); 18 | Task DropAsync(); 19 | Task CreateAsync(); 20 | Task AnyAsync(string field, TValue value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/Repositories/AccountSummaryRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data.Repositories 2 | { 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Transaction.Framework.Data.Entities; 7 | using Transaction.Framework.Data.Interface; 8 | 9 | public class AccountSummaryRepository : IAccountSummaryRepository 10 | { 11 | private readonly ApplicationDbContext _dbContext; 12 | private readonly DbSet _accountSummaryEntity; 13 | 14 | public AccountSummaryRepository(ApplicationDbContext dbContext) 15 | { 16 | _dbContext = dbContext; 17 | _accountSummaryEntity = _dbContext.Set(); 18 | } 19 | 20 | public async Task Read(int accountNumber) 21 | { 22 | return await _accountSummaryEntity.AsNoTracking() 23 | .FirstOrDefaultAsync(e => e.AccountNumber == accountNumber); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Services/Transaction/Mappers/SetIdentityAction.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using System; 3 | using Transaction.Framework.Domain; 4 | using Transaction.Framework.Extensions; 5 | using Transaction.Framework.Types; 6 | using Transaction.WebApi.Models; 7 | using Transaction.WebApi.Services; 8 | 9 | namespace Transaction.WebApi.Mappers 10 | { 11 | public class SetIdentityAction : IMappingAction 12 | { 13 | private readonly IIdentityService _identityService; 14 | 15 | public SetIdentityAction(IIdentityService identityService) 16 | { 17 | _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); 18 | } 19 | 20 | public void Process(TransactionModel source, AccountTransaction destination) 21 | { 22 | var identity = _identityService.GetIdentity(); 23 | 24 | destination.AccountNumber = identity.AccountNumber; 25 | destination.Amount = new Money(source.Amount, identity.Currency.TryParseEnum()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Publishers/ScheduleTask.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Service.Publishers 2 | { 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using Publisher.Framework.Services; 6 | using System; 7 | using System.Threading.Tasks; 8 | 9 | public class ScheduleTask : ScheduledPublisher 10 | { 11 | private readonly IPublishService _publishService; 12 | 13 | public ScheduleTask(IServiceScopeFactory serviceScopeFactory, ILogger logger, IPublishService publishService) : base(serviceScopeFactory, logger) 14 | { 15 | _publishService = publishService ?? throw new ArgumentNullException(nameof(publishService)); 16 | } 17 | 18 | protected override string Schedule => "0 0 1 * *"; 19 | 20 | public override Task ProcessInScope(IServiceProvider serviceProvider) 21 | { 22 | Console.WriteLine("Processing starts here"); 23 | _publishService.PublishAsync(DateTime.Now.AddDays(-1).ToString("MMM-yyyy")); 24 | return Task.CompletedTask; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Repository/CacheRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Repository 2 | { 3 | using Newtonsoft.Json; 4 | using StackExchange.Redis; 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | public class CacheRepository : ICacheRepository where TValue : class 9 | { 10 | private readonly IDatabase _database; 11 | 12 | public CacheRepository(IDatabase database) 13 | { 14 | _database = database; 15 | } 16 | 17 | public async Task KeyExistsAsync(string key) 18 | { 19 | return await _database.KeyExistsAsync(key); 20 | } 21 | 22 | public async Task SetAsync(string key, TValue value, TimeSpan? expiry) 23 | { 24 | await _database.KeyDeleteAsync(key); 25 | await _database.StringSetAsync(key, JsonConvert.SerializeObject(value)); 26 | await _database.KeyExpireAsync(key, expiry); 27 | } 28 | 29 | public async Task KeyDeleteAsync(string key) 30 | { 31 | await _database.KeyDeleteAsync(key); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Commands/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Publisher.Framework.Commands 6 | { 7 | public abstract class Command : ICommand 8 | { 9 | public Command() 10 | { 11 | this.CorrelationId = Guid.NewGuid(); 12 | this.ReceivedOn = DateTime.UtcNow; 13 | } 14 | 15 | public Guid CorrelationId { get; set; } 16 | public DateTime ReceivedOn { get; set; } 17 | 18 | } 19 | 20 | public class Command : Command, ICommand 21 | { 22 | public Command(T body) : base() 23 | { 24 | Payload = body; 25 | } 26 | 27 | public object Payload { get; set; } 28 | 29 | public static ICommand Create(object oBody) 30 | { 31 | return new Command((T)oBody); 32 | } 33 | 34 | public T GetBody() 35 | { 36 | return (T)Payload; 37 | } 38 | 39 | public override string ToString() 40 | { 41 | return string.Format("CreatedDate={0}, Id={1}, Type={2}", this.ReceivedOn, this.CorrelationId.ToString("N"), typeof(T).Name); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Receivers/BaseReceiver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Receiver.Service.Receivers 7 | { 8 | public abstract class BaseReceiver : IHostedService 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public BaseReceiver(ILogger logger) 13 | { 14 | _logger = logger; 15 | } 16 | 17 | public virtual Task StartAsync(CancellationToken cancellationToken) 18 | { 19 | _logger.LogInformation("Hosted Service is starting."); 20 | return ExecuteAsync(cancellationToken); 21 | } 22 | 23 | public virtual async Task StopAsync(CancellationToken cancellationToken) 24 | { 25 | _logger.LogInformation("Hosted Service is stopping."); 26 | await Task.CompletedTask; 27 | } 28 | 29 | protected virtual async Task ExecuteAsync(CancellationToken stoppingToken) 30 | { 31 | _logger.LogInformation("Hosted Service is Executing."); 32 | await Process(); 33 | } 34 | 35 | protected abstract Task Process(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Controllers/StatementController.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi.Controllers 2 | { 3 | using Microsoft.AspNetCore.Mvc; 4 | using Statement.Framework.Services; 5 | using Statement.WebApi.Services; 6 | using System; 7 | using System.Threading.Tasks; 8 | 9 | [Route("api/statement")] 10 | [ApiController] 11 | public class StatementController : ControllerBase 12 | { 13 | private readonly IStatementService _statementService; 14 | private readonly IIdentityService _identityService; 15 | 16 | public StatementController(IStatementService statementService, IIdentityService identityService) 17 | { 18 | _statementService = statementService ?? throw new ArgumentNullException(nameof(statementService)); 19 | _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); 20 | } 21 | 22 | [HttpGet("{month}")] 23 | public async Task Get(string month) 24 | { 25 | var identity = _identityService.GetIdentity(); 26 | var result = await _statementService.GetAsync(identity.AccountNumber, month); 27 | return Ok(result); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Services/Transaction/Controllers/InternalController.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Controllers 2 | { 3 | using AutoMapper; 4 | using Microsoft.AspNetCore.Mvc; 5 | using System; 6 | using System.Threading.Tasks; 7 | using Transaction.Framework.Services.Interface; 8 | using Transaction.WebApi.Models; 9 | 10 | [Route("api/internal")] 11 | [ApiController] 12 | public class InternalController : ControllerBase 13 | { 14 | private readonly ITransactionService _transactionService; 15 | private readonly IMapper _mapper; 16 | 17 | public InternalController(ITransactionService transactionService, IMapper mapper) 18 | { 19 | _transactionService = transactionService ?? throw new ArgumentNullException(nameof(transactionService)); 20 | _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); 21 | } 22 | 23 | [HttpGet("{accountNumber}/statement/{month}")] 24 | public async Task GetStatement(int accountNumber, string month) 25 | { 26 | var transactionResult = await _transactionService.Statement(accountNumber, month); 27 | return Ok(_mapper.Map(transactionResult)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Publishers/BasePublisher.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Service.Publishers 2 | { 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | public abstract class BasePublisher : IHostedService 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public BasePublisher(ILogger logger) 13 | { 14 | _logger = logger; 15 | } 16 | 17 | public virtual Task StartAsync(CancellationToken cancellationToken) 18 | { 19 | _logger.LogInformation("Hosted Service is starting."); 20 | return ExecuteAsync(cancellationToken); 21 | } 22 | 23 | public virtual async Task StopAsync(CancellationToken cancellationToken) 24 | { 25 | _logger.LogInformation("Hosted Service is stopping."); 26 | await Task.CompletedTask; 27 | } 28 | 29 | protected virtual async Task ExecuteAsync(CancellationToken stoppingToken) 30 | { 31 | _logger.LogInformation("Hosted Service is Executing."); 32 | await Process(); 33 | } 34 | 35 | protected abstract Task Process(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Services/Identity/Controllers/InternalController.cs: -------------------------------------------------------------------------------- 1 | namespace Identity.WebApi.Controllers 2 | { 3 | using Identity.WebApi.Services; 4 | using Microsoft.AspNetCore.Mvc; 5 | using System; 6 | 7 | [Route("api/internal")] 8 | [ApiController] 9 | public class InternalController : ControllerBase 10 | { 11 | private IUserService _userService; 12 | 13 | public InternalController(IUserService userService) 14 | { 15 | _userService = userService ?? throw new ArgumentNullException(nameof(userService)); 16 | } 17 | 18 | [HttpGet("accountnumbers")] 19 | public IActionResult GetAccountNumbers() 20 | { 21 | var result = _userService.GetAccountNumbers(); 22 | return Ok(result); 23 | } 24 | 25 | [HttpGet("useraccounts")] 26 | public IActionResult GetUserAccounts() 27 | { 28 | var result = _userService.GetUserAccounts(); 29 | return Ok(result); 30 | } 31 | 32 | [HttpGet("useraccount/{accountnumber}")] 33 | public IActionResult GetUserAccounts(int accountnumber) 34 | { 35 | var result = _userService.GetUserAccount(accountnumber); 36 | return Ok(result); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Frameworks/Statement/Repositories/CacheRepository.cs: -------------------------------------------------------------------------------- 1 | using StackExchange.Redis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | 8 | namespace Statement.Framework.Repositories 9 | { 10 | public class CacheRepository : ICacheRepository where TValue : class 11 | { 12 | private readonly IDatabase _database; 13 | 14 | public CacheRepository(IDatabase database) 15 | { 16 | _database = database; 17 | } 18 | 19 | public async Task KeyExistsAsync(string key) 20 | { 21 | return await _database.KeyExistsAsync(key); 22 | } 23 | 24 | 25 | public async Task GetAsync(string key) 26 | { 27 | var cacheValue = await _database.StringGetAsync(key); 28 | return JsonConvert.DeserializeObject(cacheValue); 29 | } 30 | 31 | public async Task SetAsync(string key, TValue value, TimeSpan? expiry) 32 | { 33 | await _database.KeyDeleteAsync(key); 34 | await _database.StringSetAsync(key, JsonConvert.SerializeObject(value)); 35 | await _database.KeyExpireAsync(key, expiry); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Services/PublishService.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Framework.Services 2 | { 3 | using Microsoft.Extensions.Options; 4 | using Publisher.Framework.Commands; 5 | using Publisher.Framework.Configurations; 6 | using Publisher.Framework.Messages; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | 11 | public class PublishService : IPublishService 12 | { 13 | private readonly ICommandPublisher _commandPublisher; 14 | private readonly QueueNames _queueNames; 15 | 16 | public PublishService(ICommandPublisher commandPublisher, IOptions options) 17 | { 18 | if (options == null) 19 | { 20 | throw new ArgumentNullException(nameof(options)); 21 | } 22 | 23 | _commandPublisher = commandPublisher ?? throw new ArgumentNullException(nameof(commandPublisher)); 24 | _queueNames = options.Value; 25 | } 26 | 27 | public async Task PublishAsync(string month) 28 | { 29 | await _commandPublisher.PublishAsync(_queueNames.Trigger, new TriggerMessage() { Month = month }); 30 | } 31 | 32 | public async Task PublishAsync(string month, IEnumerable accountNumbers) 33 | { 34 | await _commandPublisher.PublishAsync(_queueNames.Trigger, new TriggerMessage() { Month = month, AccountNumbers = accountNumbers }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Extensions/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Extensions 2 | { 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Transaction.Framework.Data; 7 | using Transaction.Framework.Data.Interface; 8 | using Transaction.Framework.Data.Repositories; 9 | using Transaction.Framework.Services; 10 | using Transaction.Framework.Services.Interface; 11 | using Transaction.Framework.Mappers; 12 | using AutoMapper; 13 | 14 | public static class ServiceCollectionExtension 15 | { 16 | public static IServiceCollection AddTransactionFramework(this IServiceCollection services, IConfiguration configuration) 17 | { 18 | // Service 19 | services.AddScoped(); 20 | 21 | // Repository 22 | services.AddScoped(); 23 | services.AddScoped(); 24 | 25 | // Mappers 26 | services.AddAutoMapper(x => x.AddProfile(new MappingProfile())); 27 | 28 | // Connection String 29 | services.AddDbContext(options => options.UseSqlServer(configuration.GetConnectionString("SqlServerConnection"))); 30 | 31 | return services; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Services/Identity/Startup.cs: -------------------------------------------------------------------------------- 1 | using Identity.WebApi.Helpers; 2 | using Identity.WebApi.Services; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Identity.WebApi 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 24 | 25 | var appSettingSection = Configuration.GetSection("AppSettings"); 26 | services.Configure(appSettingSection); 27 | 28 | services.AddScoped(); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | 39 | app.UseMvc(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Services/Transaction/Mappers/ModelMappingProfile.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Mappers 2 | { 3 | using AutoMapper; 4 | using Transaction.Framework.Domain; 5 | using Transaction.WebApi.Models; 6 | 7 | public class ModelMappingProfile : Profile 8 | { 9 | public ModelMappingProfile() 10 | { 11 | CreateMap() 12 | .ForMember(dest => dest.Date, opt => opt.MapFrom(o => o.Date)) 13 | .ForMember(dest => dest.Description, opt => opt.MapFrom(o => o.Description)) 14 | .AfterMap() 15 | .ForAllOtherMembers(opts => opts.Ignore()); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Balance, opt => opt.MapFrom(o => o.Balance.Amount.ToString("N"))) 19 | .ForMember(dest => dest.Currency, opt => opt.MapFrom(o => o.Balance.Currency.ToString())); 20 | 21 | CreateMap() 22 | .ForMember(dest => dest.Amount, opt => opt.MapFrom(o => o.Amount.Amount.ToString("N"))) 23 | .ForMember(dest => dest.CurrentBalance, opt => opt.MapFrom(o => o.CurrentBalance.Amount.ToString("N"))); 24 | 25 | CreateMap() 26 | .ForMember(dest => dest.Date, opt => opt.MapFrom(o => o.Date.StartDate.ToString("MMM-yyyy"))); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Controllers/PublishController.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Service.Controllers 2 | { 3 | using Microsoft.AspNetCore.Mvc; 4 | using Publisher.Framework.Services; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | 9 | [Route("api/publish")] 10 | [ApiController] 11 | public class PublishController : ControllerBase 12 | { 13 | private readonly IPublishService _publishService; 14 | 15 | public PublishController(IPublishService publishService) 16 | { 17 | _publishService = publishService ?? throw new ArgumentNullException(nameof(publishService)); 18 | } 19 | 20 | [HttpPost("statement")] 21 | public async Task Post() 22 | { 23 | await _publishService.PublishAsync(DateTime.Now.AddMonths(-1).ToString("MMM-yyyy")); 24 | return NoContent(); 25 | } 26 | 27 | [HttpPost("statement/{month}")] 28 | public async Task Post([FromRoute] string month) 29 | { 30 | await _publishService.PublishAsync(month); 31 | return NoContent(); 32 | } 33 | 34 | [HttpPost("statement/{month}/accountnumbers")] 35 | public async Task Post([FromRoute] string month, [FromBody] List accountNumbers) 36 | { 37 | await _publishService.PublishAsync(month, accountNumbers); 38 | return NoContent(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Statement.Framework.Extensions; 9 | using Statement.WebApi.Services; 10 | 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddStatementFramework(Configuration); 24 | services.AddScoped(); 25 | services.AddHttpContextAccessor(); 26 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 27 | } 28 | 29 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 30 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 31 | { 32 | if (env.IsDevelopment()) 33 | { 34 | app.UseDeveloperExceptionPage(); 35 | } 36 | 37 | app.UseMvc(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Publishers/ScheduledPublisher.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using NCrontab; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Publisher.Service.Publishers 11 | { 12 | public abstract class ScheduledPublisher : ScopedPublisher 13 | { 14 | private CrontabSchedule _schedule; 15 | private DateTime _nextRun; 16 | 17 | protected abstract string Schedule { get; } 18 | 19 | public ScheduledPublisher(IServiceScopeFactory serviceScopeFactory, ILogger logger) : base(serviceScopeFactory, logger) 20 | { 21 | _schedule = CrontabSchedule.Parse(Schedule); 22 | _nextRun = _schedule.GetNextOccurrence(DateTime.Now); 23 | } 24 | 25 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 26 | { 27 | do 28 | { 29 | var now = DateTime.Now; 30 | var nextrun = _schedule.GetNextOccurrence(now); 31 | 32 | if (now > _nextRun) 33 | { 34 | await Process(); 35 | _nextRun = _schedule.GetNextOccurrence(DateTime.Now); 36 | } 37 | 38 | await Task.Delay(5000, stoppingToken); 39 | } 40 | while (!stoppingToken.IsCancellationRequested); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Receiver.Service 8 | { 9 | public static class Constants 10 | { 11 | public const string MediaTypeAppJson = "application/json"; 12 | } 13 | 14 | public static class NamedHttpClients 15 | { 16 | public const string IdentityServiceClient = "idnsrvclient"; 17 | public const string TransactionServiceClient = "txnsrvclient"; 18 | } 19 | 20 | public class HttpConfigAttribs 21 | { 22 | public string Name { get; set; } 23 | public string BaseUrl { get; set; } 24 | public string MediaType { get; set; } 25 | } 26 | 27 | public class BaseUrlSettings 28 | { 29 | public string IdentityApiBaseUrl { get; set; } 30 | public string TransactionApiBaseUrl { get; set; } 31 | } 32 | 33 | public enum TransactionType 34 | { 35 | Deposit = 1, 36 | Withdrawal = 2 37 | } 38 | 39 | public enum Currency 40 | { 41 | Unknown = 0, 42 | 43 | [Description("United States dollar")] 44 | USD = 840, 45 | 46 | [Description("Pound sterling")] 47 | GBP = 826, 48 | 49 | [Description("Euro")] 50 | EUR = 978, 51 | 52 | [Description("Indian rupee")] 53 | INR = 356 54 | } 55 | 56 | public class CacheExpiry 57 | { 58 | public int ExpiryInSeconds { get; set; } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Validation/TransactionValidation.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Validation 2 | { 3 | using System.Threading.Tasks; 4 | using Transaction.Framework.Domain; 5 | using Transaction.Framework.Exceptions; 6 | using Transaction.Framework.Types; 7 | 8 | public static class TransactionValidation 9 | { 10 | public static async Task Validate(this AccountTransaction accountTransaction, AccountSummary accountSummary) 11 | { 12 | var amount = accountTransaction.Amount; 13 | 14 | if (amount.Currency == Currency.Unknown) 15 | { 16 | throw new InvalidCurrencyException(amount.Currency.ToString()); 17 | } 18 | 19 | if (amount <= 0) 20 | { 21 | throw new InvalidAmountException(amount.Amount); 22 | } 23 | 24 | if (accountTransaction.TransactionType == TransactionType.Withdrawal) 25 | { 26 | if (amount > accountSummary.Balance) 27 | { 28 | throw new InsufficientBalanceException(); 29 | } 30 | } 31 | 32 | await Task.CompletedTask; 33 | } 34 | 35 | public static async Task Validate(this AccountSummary accountSummary, int accountNumber) 36 | { 37 | if (accountSummary == null) 38 | { 39 | throw new InvalidAccountNumberException(accountNumber); 40 | } 41 | 42 | await Task.CompletedTask; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Clients/IdentityClient.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Clients 2 | { 3 | using Receiver.Service.Helpers; 4 | using Receiver.Service.Models; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | public class IdentityClient : HttpClientBase, IIdentityClient 10 | { 11 | public IdentityClient(IHttpClientFactory httpClientFactory) : base(httpClientFactory) { } 12 | 13 | public async Task> GetAccountNumbers() 14 | { 15 | var relativeUri = "api/internal/accountnumbers"; 16 | var result = await GetAsync>(relativeUri); 17 | return result; 18 | } 19 | 20 | public async Task> GetUserAccounts() 21 | { 22 | var relativeUri = "api/internal/useraccounts"; 23 | var result = await GetAsync>(relativeUri); 24 | return result; 25 | } 26 | 27 | public async Task GetUserAccount(int accountnumber) 28 | { 29 | var relativeUri = $"api/internal/useraccount/{accountnumber}"; 30 | var result = await GetAsync(relativeUri); 31 | return result; 32 | } 33 | 34 | protected override HttpClient GetScopedHttpClient() 35 | { 36 | var httpClient = _httpClientFactory.CreateClient(NamedHttpClients.IdentityServiceClient); 37 | return httpClient; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Documents/AccountStatement.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Documents 2 | { 3 | using MongoDB.Bson.Serialization.Attributes; 4 | using System.Collections.Generic; 5 | 6 | public class AccountStatement 7 | { 8 | [BsonId] 9 | public string Key { get; set; } 10 | [BsonElement("name")] 11 | public string Name { get; set; } 12 | [BsonElement("accountnumber")] 13 | public int AccountNumber { get; set; } 14 | [BsonElement("currency")] 15 | public string Currency { get; set; } 16 | [BsonElement("startdate")] 17 | public string StartDate { get; set; } 18 | [BsonElement("enddate")] 19 | public string EndDate { get; set; } 20 | [BsonElement("openingbalance")] 21 | public decimal? OpeningBalance { get; set; } 22 | [BsonElement("closingbalance")] 23 | public decimal? ClosingBalance { get; set; } 24 | [BsonElement("transactions")] 25 | public IEnumerable TransactionDetails { get; set; } 26 | } 27 | 28 | public class AccountTransaction 29 | { 30 | [BsonElement("date")] 31 | public string Date { get; set; } 32 | [BsonElement("description")] 33 | public string TransactionDetail { get; set; } 34 | [BsonElement("withdrawal")] 35 | public string Withdrawal { get; set; } 36 | [BsonElement("deposit")] 37 | public string Deposit { get; set; } 38 | [BsonElement("balance")] 39 | public decimal Balance { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Documents/AccountStatement.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Documents 2 | { 3 | using MongoDB.Bson.Serialization.Attributes; 4 | using System.Collections.Generic; 5 | 6 | public class AccountStatement 7 | { 8 | [BsonId] 9 | public string Key { get; set; } 10 | [BsonElement("name")] 11 | public string Name { get; set; } 12 | [BsonElement("accountnumber")] 13 | public int AccountNumber { get; set; } 14 | [BsonElement("currency")] 15 | public string Currency { get; set; } 16 | [BsonElement("startdate")] 17 | public string StartDate { get; set; } 18 | [BsonElement("enddate")] 19 | public string EndDate { get; set; } 20 | [BsonElement("openingbalance")] 21 | public decimal? OpeningBalance { get; set; } 22 | [BsonElement("closingbalance")] 23 | public decimal? ClosingBalance { get; set; } 24 | [BsonElement("transactions")] 25 | public IEnumerable TransactionDetails { get; set; } 26 | } 27 | 28 | public class AccountTransaction 29 | { 30 | [BsonElement("date")] 31 | public string Date { get; set; } 32 | [BsonElement("description")] 33 | public string TransactionDetail { get; set; } 34 | [BsonElement("withdrawal")] 35 | public string Withdrawal { get; set; } 36 | [BsonElement("deposit")] 37 | public string Deposit { get; set; } 38 | [BsonElement("balance")] 39 | public decimal Balance { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Frameworks/Publisher/Extensions/ServiceExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Framework.Extensions 2 | { 3 | using EasyNetQ; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | using Publisher.Framework.Commands; 8 | using Publisher.Framework.Configurations; 9 | using Publisher.Framework.Services; 10 | 11 | public static class ServiceExtension 12 | { 13 | public static IServiceCollection AddSingletonCommandPublisher(this IServiceCollection services, IConfiguration configuration) 14 | { 15 | services.TryAddSingleton(); 16 | services.TryAddSingleton(); 17 | services.AddServiceConfig(configuration); 18 | return services; 19 | } 20 | 21 | public static IServiceCollection AddScopedCommandPublisher(this IServiceCollection services, IConfiguration configuration) 22 | { 23 | services.TryAddScoped(); 24 | services.TryAddScoped(); 25 | services.AddServiceConfig(configuration); 26 | return services; 27 | } 28 | 29 | private static IServiceCollection AddServiceConfig(this IServiceCollection services, IConfiguration configuration) 30 | { 31 | services.AddSingleton(RabbitHutch.CreateBus(configuration.GetValue("RabbitMQ:ConnectionString"))); 32 | services.Configure(options => configuration.GetSection("QueueNames").Bind(options)); 33 | return services; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Services/Transaction/Services/IdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Services 2 | { 3 | using Microsoft.AspNetCore.Http; 4 | using System; 5 | using System.IdentityModel.Tokens.Jwt; 6 | using System.Linq; 7 | using Transaction.WebApi.Models; 8 | 9 | public class IdentityService : IIdentityService 10 | { 11 | private IHttpContextAccessor _context; 12 | 13 | public IdentityService(IHttpContextAccessor context) 14 | { 15 | _context = context ?? throw new ArgumentNullException(nameof(context)); 16 | } 17 | 18 | public IdentityModel GetIdentity() 19 | { 20 | string authorizationHeader = _context.HttpContext.Request.Headers["Authorization"]; 21 | 22 | if (authorizationHeader != null) 23 | { 24 | var tokenHandler = new JwtSecurityTokenHandler(); 25 | var token = authorizationHeader.Split(" ")[1]; 26 | var paresedToken = tokenHandler.ReadJwtToken(token); 27 | 28 | var account = paresedToken.Claims 29 | .Where(c => c.Type == "accountnumber") 30 | .FirstOrDefault(); 31 | 32 | var name = paresedToken.Claims 33 | .Where(c => c.Type == "name") 34 | .FirstOrDefault(); 35 | 36 | var currency = paresedToken.Claims 37 | .Where(c => c.Type == "currency") 38 | .FirstOrDefault(); 39 | 40 | return new IdentityModel() { 41 | AccountNumber = Convert.ToInt32(account.Value), 42 | FullName = name.Value, 43 | Currency = currency.Value 44 | }; 45 | } 46 | 47 | throw new ArgumentNullException("accountnumber"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Background/Publisher/Publisher.Service/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace Publisher.Service 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Publisher.Framework.Extensions; 9 | using Publisher.Service.Publishers; 10 | 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddCommandConfiguration(Configuration); 24 | services.AddSingleton(); 25 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 26 | } 27 | 28 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 29 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 30 | { 31 | if (env.IsDevelopment()) 32 | { 33 | app.UseDeveloperExceptionPage(); 34 | } 35 | 36 | app.UseMvc(); 37 | } 38 | } 39 | 40 | internal static class ServiceExtensions 41 | { 42 | public static IServiceCollection AddCommandConfiguration(this IServiceCollection services, IConfiguration config) 43 | { 44 | // Command Config 45 | services.AddSingletonCommandPublisher(config); 46 | return services; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Services/IdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi.Services 2 | { 3 | using Microsoft.AspNetCore.Http; 4 | using Statement.WebApi.Models; 5 | using System; 6 | using System.IdentityModel.Tokens.Jwt; 7 | using System.Linq; 8 | 9 | public class IdentityService : IIdentityService 10 | { 11 | private IHttpContextAccessor _context; 12 | 13 | public IdentityService(IHttpContextAccessor context) 14 | { 15 | _context = context ?? throw new ArgumentNullException(nameof(context)); 16 | } 17 | 18 | public IdentityModel GetIdentity() 19 | { 20 | string authorizationHeader = _context.HttpContext.Request.Headers["Authorization"]; 21 | 22 | if (authorizationHeader != null) 23 | { 24 | var tokenHandler = new JwtSecurityTokenHandler(); 25 | var token = authorizationHeader.Split(" ")[1]; 26 | var paresedToken = tokenHandler.ReadJwtToken(token); 27 | 28 | var account = paresedToken.Claims 29 | .Where(c => c.Type == "accountnumber") 30 | .FirstOrDefault(); 31 | 32 | var name = paresedToken.Claims 33 | .Where(c => c.Type == "name") 34 | .FirstOrDefault(); 35 | 36 | var currency = paresedToken.Claims 37 | .Where(c => c.Type == "currency") 38 | .FirstOrDefault(); 39 | 40 | return new IdentityModel() 41 | { 42 | AccountNumber = Convert.ToInt32(account.Value), 43 | FullName = name.Value, 44 | Currency = currency.Value 45 | }; 46 | } 47 | 48 | throw new ArgumentNullException("accountnumber"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Services/StatementService.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Services 2 | { 3 | using Microsoft.Extensions.Options; 4 | using Statement.Framework.Documents; 5 | using Statement.Framework.Repositories; 6 | using System; 7 | using System.Threading.Tasks; 8 | 9 | public class StatementService : IStatementService 10 | { 11 | private readonly IDocumentRepository _documentRepository; 12 | private readonly ICacheRepository _cacheRepository; 13 | private readonly int _cacheExpiryInSeconds; 14 | 15 | public StatementService(IDocumentRepository documentRepository, ICacheRepository cacheRepository, IOptions options) 16 | { 17 | if (options == null) 18 | { 19 | throw new ArgumentNullException(nameof(options)); 20 | } 21 | 22 | _documentRepository = documentRepository ?? throw new ArgumentNullException(nameof(documentRepository)); 23 | _cacheRepository = cacheRepository ?? throw new ArgumentNullException(nameof(cacheRepository)); 24 | _cacheExpiryInSeconds = options.Value.ExpiryInSeconds; 25 | } 26 | 27 | public async Task GetAsync(int accountNumber, string month) 28 | { 29 | var key = $"{accountNumber}-{month}"; 30 | 31 | if (_cacheRepository.KeyExistsAsync(key).Result) 32 | { 33 | return await _cacheRepository.GetAsync(key); 34 | } 35 | else 36 | { 37 | var document = await _documentRepository.GetAsync(key); 38 | 39 | if(document != null) 40 | { 41 | await _cacheRepository.SetAsync(key, document, TimeSpan.FromSeconds(_cacheExpiryInSeconds)); 42 | } 43 | 44 | return document; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Exceptions/TransactionException.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Exceptions 2 | { 3 | using System; 4 | using Transaction.Framework.Types; 5 | 6 | public abstract class TransactionException : Exception 7 | { 8 | public TransactionException(string message) 9 | : base(message) 10 | { } 11 | 12 | public abstract int ErrorCode { get; } 13 | } 14 | 15 | public class InsufficientBalanceException : TransactionException 16 | { 17 | public InsufficientBalanceException() 18 | : base($"This operation can not be performed due to insufficient balance in the account.") 19 | { } 20 | 21 | public override int ErrorCode => 22 | TransactionErrorCode.InsufficientBalance; 23 | } 24 | 25 | public class InvalidAccountNumberException : TransactionException 26 | { 27 | public InvalidAccountNumberException(int accountNumber) 28 | : base($"This account {accountNumber} does not exist.") 29 | { } 30 | 31 | public override int ErrorCode => 32 | TransactionErrorCode.AccountDoesNotExistError; 33 | } 34 | 35 | public class InvalidAmountException : TransactionException 36 | { 37 | public InvalidAmountException(decimal amount) 38 | : base($"This operation can not be performed for {amount} amount.") 39 | { } 40 | 41 | public override int ErrorCode => 42 | TransactionErrorCode.InvalidAmount; 43 | } 44 | 45 | public class InvalidCurrencyException : TransactionException 46 | { 47 | public InvalidCurrencyException(string currency) 48 | : base($"This operation can not be performed with {currency} currency.") 49 | { } 50 | 51 | public override int ErrorCode => 52 | TransactionErrorCode.InvalidCurrencyError; 53 | } 54 | 55 | public class CurrencyMismatchException : TransactionException 56 | { 57 | public CurrencyMismatchException(Currency c1, Currency c2) 58 | : base($"This operation cannot be performed between {c1} and {c2}.") 59 | { } 60 | 61 | public override int ErrorCode => 62 | TransactionErrorCode.CurrencyMismatchError; 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Receivers/ScopedReceiver.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Receivers 2 | { 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using Publisher.Framework.Commands; 6 | using Receiver.Service.Helpers; 7 | using System; 8 | using System.Net.Http; 9 | 10 | using System.Threading.Tasks; 11 | public abstract class ScopedReceiver : BaseReceiver 12 | { 13 | private readonly IServiceScopeFactory _serviceScopeFactory; 14 | private readonly IRetryHelper _retryHelper; 15 | private readonly ILogger _logger; 16 | 17 | public ScopedReceiver(IServiceScopeFactory serviceScopeFactory, IRetryHelper retryHelper, ILogger logger) : base(logger) 18 | { 19 | _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); 20 | _retryHelper = retryHelper ?? throw new ArgumentNullException(nameof(retryHelper)); 21 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 22 | } 23 | 24 | protected override async Task Process() 25 | { 26 | using (var scope = _serviceScopeFactory.CreateScope()) 27 | { 28 | await ProcessInScope(scope.ServiceProvider); 29 | } 30 | } 31 | 32 | protected async Task Execute(ICommand qMessage) 33 | { 34 | try 35 | { 36 | await OnMessageReceived(qMessage); 37 | } 38 | catch (HttpRequestException ex) 39 | { 40 | _logger.LogError("ScopedReceiver", ex, "qMessage processing failed in the first attempt"); 41 | 42 | try 43 | { 44 | await _retryHelper.Do(OnMessageReceived, qMessage, new TimeSpan(0, 0, 10)); 45 | } 46 | catch (Exception e) 47 | { 48 | // throwing the exception pushes the message in to error queue 49 | throw e; 50 | } 51 | } 52 | } 53 | 54 | public abstract Task ProcessInScope(IServiceProvider serviceProvider); 55 | public abstract Task OnMessageReceived(ICommand qMessage); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Types/Money.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Types 2 | { 3 | public struct Money 4 | { 5 | public Money(decimal amount, Currency currency) 6 | { 7 | Amount = amount; 8 | Currency = currency; 9 | } 10 | 11 | public decimal Amount { get; private set; } 12 | public Currency Currency { get; private set; } 13 | 14 | public Money ToAmount(decimal amount) => new Money(amount, Currency); 15 | 16 | public static Money operator +(decimal d, Money m) 17 | { 18 | return new Money(m.Amount + d, m.Currency); 19 | } 20 | 21 | public static Money operator +(Money m, decimal d) 22 | { 23 | return d + m; 24 | } 25 | 26 | public static Money operator -(Money m, decimal d) 27 | { 28 | return new Money(m.Amount - d, m.Currency); 29 | } 30 | 31 | public static bool operator >(Money m, decimal d) 32 | { 33 | return m.Amount > d; 34 | } 35 | 36 | public static bool operator <(Money m, decimal d) 37 | { 38 | return m.Amount < d; 39 | } 40 | 41 | public static bool operator >=(Money m, decimal d) 42 | { 43 | return m.Amount >= d; 44 | } 45 | 46 | public static bool operator <=(Money m, decimal d) 47 | { 48 | return m.Amount <= d; 49 | } 50 | 51 | public static Money operator +(Money m1, Money m2) 52 | { 53 | return m1 + m2.Amount; 54 | } 55 | 56 | public static Money operator -(Money m1, Money m2) 57 | { 58 | return m1 - m2.Amount; 59 | } 60 | 61 | public static bool operator >(Money m1, Money m2) 62 | { 63 | return m1.Amount > m2.Amount; 64 | } 65 | 66 | public static bool operator <(Money m1, Money m2) 67 | { 68 | return m1.Amount < m2.Amount; 69 | } 70 | 71 | public static bool operator >=(Money m1, Money m2) 72 | { 73 | return m1.Amount >= m2.Amount; 74 | } 75 | 76 | public static bool operator <=(Money m1, Money m2) 77 | { 78 | return m1.Amount <= m2.Amount; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Receivers/TriggerReceiver.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Receivers 2 | { 3 | using EasyNetQ; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using Publisher.Framework.Commands; 8 | using Publisher.Framework.Configurations; 9 | using Publisher.Framework.Messages; 10 | using Receiver.Service.Helpers; 11 | using Receiver.Service.Processors; 12 | using System; 13 | using System.Threading.Tasks; 14 | 15 | public class TriggerReceiver : ScopedReceiver 16 | { 17 | private readonly IBus _bus; 18 | private readonly ILogger _logger; 19 | private readonly Func _processor; 20 | private readonly QueueNames _queueNames; 21 | 22 | public TriggerReceiver(IBus bus, Func processor, IOptions options, IServiceScopeFactory serviceScopeFactory, IRetryHelper retryHelper, ILogger logger) : base(serviceScopeFactory, retryHelper, logger) 23 | { 24 | if (options == null) 25 | { 26 | throw new ArgumentNullException(nameof(options)); 27 | } 28 | 29 | if (serviceScopeFactory == null) 30 | { 31 | throw new ArgumentNullException(nameof(serviceScopeFactory)); 32 | } 33 | 34 | if (retryHelper == null) 35 | { 36 | throw new ArgumentNullException(nameof(retryHelper)); 37 | } 38 | 39 | _bus = bus ?? throw new ArgumentNullException(nameof(bus)); 40 | _processor = processor ?? throw new ArgumentNullException(nameof(processor)); 41 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 42 | 43 | _queueNames = options.Value; 44 | } 45 | 46 | public override async Task OnMessageReceived(ICommand qMessage) 47 | { 48 | await _processor("trigger").ProcessMessageAsync(qMessage); 49 | } 50 | 51 | public override async Task ProcessInScope(IServiceProvider serviceProvider) 52 | { 53 | await Task.Run(() => _bus.Receive(_queueNames.Trigger, async (qMessage) => { 54 | await Execute(qMessage); 55 | })); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Receivers/StatementReceiver.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Receivers 2 | { 3 | using EasyNetQ; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using Publisher.Framework.Commands; 8 | using Publisher.Framework.Configurations; 9 | using Publisher.Framework.Messages; 10 | using Receiver.Service.Helpers; 11 | using Receiver.Service.Processors; 12 | using System; 13 | using System.Threading.Tasks; 14 | 15 | public class StatementReceiver : ScopedReceiver 16 | { 17 | private readonly IBus _bus; 18 | private readonly ILogger _logger; 19 | private readonly Func _processor; 20 | private readonly QueueNames _queueNames; 21 | 22 | public StatementReceiver(IBus bus, Func processor, IOptions options, IServiceScopeFactory serviceScopeFactory, IRetryHelper retryHelper, ILogger logger) : base(serviceScopeFactory, retryHelper, logger) 23 | { 24 | if (options == null) 25 | { 26 | throw new ArgumentNullException(nameof(options)); 27 | } 28 | 29 | if (serviceScopeFactory == null) 30 | { 31 | throw new ArgumentNullException(nameof(serviceScopeFactory)); 32 | } 33 | 34 | if (retryHelper == null) 35 | { 36 | throw new ArgumentNullException(nameof(retryHelper)); 37 | } 38 | 39 | _bus = bus ?? throw new ArgumentNullException(nameof(bus)); 40 | _processor = processor ?? throw new ArgumentNullException(nameof(processor)); 41 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 42 | 43 | _queueNames = options.Value; 44 | } 45 | 46 | public override async Task OnMessageReceived(ICommand qMessage) 47 | { 48 | await _processor("statement").ProcessMessageAsync(qMessage); 49 | } 50 | 51 | public override async Task ProcessInScope(IServiceProvider serviceProvider) 52 | { 53 | await Task.Run(() => _bus.Receive(_queueNames.Statement, async (qMessage) => { 54 | await Execute(qMessage); 55 | })); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Frameworks/Statement/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.Framework.Extensions 2 | { 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using MongoDB.Driver; 6 | using StackExchange.Redis; 7 | using Statement.Framework.Documents; 8 | using Statement.Framework.Repositories; 9 | using Statement.Framework.Services; 10 | using System; 11 | 12 | public static class ServiceCollectionExtensions 13 | { 14 | public static IServiceCollection AddStatementFramework(this IServiceCollection services, IConfiguration configuration) 15 | { 16 | services.AddScoped(); 17 | services.AddScoped(typeof(IDocumentRepository<>), typeof(DocumentRepository<>)); 18 | services.AddMongoConfiguration(configuration); 19 | services.AddSingleton(typeof(ICacheRepository<>), typeof(CacheRepository<>)); 20 | services.AddRedisConfiguration(configuration); 21 | services.Configure(options => 22 | { 23 | options.ExpiryInSeconds = int.Parse(configuration.GetSection("CacheExpiryInSeconds").Value); 24 | }); 25 | 26 | return services; 27 | } 28 | 29 | private static IServiceCollection AddMongoConfiguration(this IServiceCollection services, IConfiguration config) 30 | { 31 | // Mongo Config 32 | var client = new MongoClient(config.GetSection("MongoConfiguration:ConnectionString").Value); 33 | var database = client.GetDatabase(config.GetSection("MongoConfiguration:Database").Value); 34 | var collection = database.GetCollection(typeof(TDocument).Name.ToLower()); 35 | 36 | services.AddScoped(_ => collection); 37 | 38 | return services; 39 | } 40 | 41 | public static IServiceCollection AddRedisConfiguration(this IServiceCollection services, IConfiguration config) 42 | { 43 | var lazyConnection = new Lazy(() => ConnectionMultiplexer.Connect(config.GetSection("RedisCache:ConnectionString").Value)); 44 | IDatabase _database = lazyConnection.Value.GetDatabase(); 45 | services.AddSingleton(_database); 46 | 47 | return services; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Services/Transaction/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Controllers 2 | { 3 | using Transaction.WebApi.Models; 4 | using AutoMapper; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Threading.Tasks; 7 | using Transaction.Framework.Domain; 8 | using Transaction.Framework.Services.Interface; 9 | using Transaction.WebApi.Services; 10 | using System; 11 | 12 | [Route("api/account")] 13 | [ApiController] 14 | public class AccountController : ControllerBase 15 | { 16 | private readonly ITransactionService _transactionService; 17 | private readonly IIdentityService _identityService; 18 | private readonly IMapper _mapper; 19 | 20 | public AccountController(ITransactionService transactionService, IIdentityService identityService, IMapper mapper) 21 | { 22 | _transactionService = transactionService ?? throw new ArgumentNullException(nameof(transactionService)); 23 | _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); 24 | _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); 25 | } 26 | 27 | [HttpGet("balance")] 28 | public async Task Balance() 29 | { 30 | var accountNumber = _identityService.GetIdentity().AccountNumber; 31 | var transactionResult = await _transactionService.Balance(accountNumber); 32 | return Ok(_mapper.Map(transactionResult)); 33 | } 34 | 35 | [HttpPost("deposit")] 36 | public async Task Deposit([FromBody] TransactionModel accountTransactionModel) 37 | { 38 | var accountTransaction = _mapper.Map(accountTransactionModel); 39 | var result = await _transactionService.Deposit(accountTransaction); 40 | return Created(string.Empty, _mapper.Map(result)); 41 | } 42 | 43 | [HttpPost("withdraw")] 44 | public async Task Withdraw([FromBody] TransactionModel accountTransactionModel) 45 | { 46 | var accountTransaction = _mapper.Map(accountTransactionModel); 47 | var result = await _transactionService.Withdraw(accountTransaction); 48 | return Created(string.Empty, _mapper.Map(result)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "ReRoutes": [ 3 | { 4 | "DownstreamPathTemplate": "/api/account/balance", 5 | "DownstreamScheme": "http", 6 | "DownstreamHostAndPorts": [ 7 | { 8 | "Host": "localhost", 9 | "Port": 60243 10 | } 11 | ], 12 | "UpstreamPathTemplate": "/account/balance", 13 | "UpstreamHttpMethod": [ "Get" ], 14 | "AuthenticationOptions": { 15 | "AuthenticationProviderKey": "Bearer", 16 | "AllowedScopes": [] 17 | } 18 | }, 19 | { 20 | "DownstreamPathTemplate": "/api/account/deposit", 21 | "DownstreamScheme": "http", 22 | "DownstreamHostAndPorts": [ 23 | { 24 | "Host": "localhost", 25 | "Port": 60243 26 | } 27 | ], 28 | "UpstreamPathTemplate": "/account/deposit", 29 | "UpstreamHttpMethod": [ "Post" ], 30 | "AuthenticationOptions": { 31 | "AuthenticationProviderKey": "Bearer", 32 | "AllowedScopes": [] 33 | } 34 | }, 35 | { 36 | "DownstreamPathTemplate": "/api/account/withdraw", 37 | "DownstreamScheme": "http", 38 | "DownstreamHostAndPorts": [ 39 | { 40 | "Host": "localhost", 41 | "Port": 60243 42 | } 43 | ], 44 | "UpstreamPathTemplate": "/account/withdraw", 45 | "UpstreamHttpMethod": [ "Post" ], 46 | "AuthenticationOptions": { 47 | "AuthenticationProviderKey": "Bearer", 48 | "AllowedScopes": [] 49 | } 50 | }, 51 | { 52 | "DownstreamPathTemplate": "/api/statement/{month}", 53 | "DownstreamScheme": "http", 54 | "DownstreamHostAndPorts": [ 55 | { 56 | "Host": "localhost", 57 | "Port": 55321 58 | } 59 | ], 60 | "UpstreamPathTemplate": "/statement/{month}", 61 | "UpstreamHttpMethod": [ "Get" ], 62 | "AuthenticationOptions": { 63 | "AuthenticationProviderKey": "Bearer", 64 | "AllowedScopes": [] 65 | } 66 | }, 67 | { 68 | "DownstreamPathTemplate": "/api/user/authenticate", 69 | "DownstreamScheme": "http", 70 | "DownstreamHostAndPorts": [ 71 | { 72 | "Host": "localhost", 73 | "Port": 54203 74 | } 75 | ], 76 | "UpstreamPathTemplate": "/user/authenticate", 77 | "UpstreamHttpMethod": [ "Post" ] 78 | } 79 | ], 80 | "GlobalConfiguration": { 81 | "UseServiceDiscovery": false 82 | } 83 | } -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Mappers/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Mappers 2 | { 3 | using AutoMapper; 4 | using System; 5 | using Transaction.Framework.Data.Entities; 6 | using Transaction.Framework.Extensions; 7 | using Transaction.Framework.Domain; 8 | using Transaction.Framework.Types; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | public class MappingProfile : Profile 13 | { 14 | public MappingProfile() 15 | { 16 | CreateMap() 17 | .ForMember(dest => dest.Balance, opt => opt.MapFrom(o => new Money(o.Balance, o.Currency.TryParseEnum()))); 18 | 19 | CreateMap() 20 | .ForMember(dest => dest.Date, opt => opt.MapFrom(o => o.Date)) 21 | .ForMember(dest => dest.Description, opt => opt.MapFrom(o => o.Description)) 22 | .ForMember(dest => dest.TransactionType, opt => opt.MapFrom(o => o.TransactionType.ToString())) 23 | .ForMember(dest => dest.Amount, opt => opt.MapFrom(o => o.Amount.Amount)) 24 | .ForMember(dest => dest.CurrentBalance, opt => opt.MapFrom(o => o.CurrentBalance.Amount)); 25 | 26 | CreateMap() 27 | .ForMember(dest => dest.Balance, opt => opt.MapFrom(o => o.Balance.Amount)) 28 | .ForMember(dest => dest.Currency, opt => opt.MapFrom(o => o.Balance.Currency.ToString())) 29 | .ForMember(dest => dest.AccountTransactions, opt => opt.Ignore()); 30 | 31 | CreateMap() 32 | .ForMember(dest => dest.IsSuccessful, opt => opt.MapFrom(o => o.TransactionId > 0)) 33 | .ForMember(dest => dest.Message, opt => opt.MapFrom(o => o.TransactionId > 0 ? StringResources.TransactionSuccessfull : StringResources.TransactionFailed)) 34 | .ForMember(dest => dest.Balance, opt => opt.Ignore()); 35 | 36 | CreateMap() 37 | .ForMember(dest => dest.Balance, opt => opt.MapFrom(o => o.Balance)) 38 | .ForMember(dest => dest.IsSuccessful, opt => opt.MapFrom(o => true)) 39 | .ForMember(dest => dest.Message, opt => opt.MapFrom(o => StringResources.TransactionSuccessfull)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Services/Transaction/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi 2 | { 3 | using Transaction.WebApi.Middlewares; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Swashbuckle.AspNetCore.Swagger; 11 | using Transaction.Framework.Extensions; 12 | using Transaction.WebApi.Services; 13 | 14 | public class Startup 15 | { 16 | public Startup(IConfiguration configuration) 17 | { 18 | Configuration = configuration; 19 | } 20 | 21 | public IConfiguration Configuration { get; } 22 | 23 | // This method gets called by the runtime. Use this method to add services to the container. 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | services.AddHttpContextAccessor(); 27 | services.AddTransactionFramework(Configuration); 28 | services.AddScoped(); 29 | services.AddScoped(); 30 | //services.AddApplicationInsightsTelemetry(Configuration); 31 | services.AddSwaggerGen(c => { 32 | c.SwaggerDoc("v1", new Info { Title = "Simple Transaction Processing", Version = "v1" }); 33 | }); 34 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory log) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | app.UseExceptionHandlerMiddleware(); 46 | //log.AddApplicationInsights(app.ApplicationServices, LogLevel.Information); 47 | app.UseSwagger(); 48 | app.UseSwaggerUI(c => { 49 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "Simple Transaction Processing v1"); 50 | }); 51 | app.UseMvc(); 52 | } 53 | } 54 | 55 | public static class AppBuilderExtension 56 | { 57 | public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder) 58 | { 59 | return builder.UseMiddleware(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/App/Console/Models.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SimpleBanking.ConsoleApp 5 | { 6 | public static class Models 7 | { 8 | public class TransactionData 9 | { 10 | public DateTime Date { get; set; } 11 | public string TransactionDetails { get; set; } 12 | public decimal? Withdrawals { get; set; } 13 | public decimal? Deposits { get; set; } 14 | public decimal Balance { get; set; } 15 | } 16 | 17 | public class TransactionInput 18 | { 19 | public DateTime Date { get; set; } 20 | public TransactionType TransactionType { get; set; } 21 | public decimal Amount { get; set; } 22 | public string Description { get; set; } 23 | } 24 | 25 | public class TransactionResult 26 | { 27 | public int? AccountNumber { get; set; } 28 | public bool IsSuccessful { get; set; } 29 | public decimal? Balance { get; set; } 30 | public string Currency { get; set; } 31 | public string Message { get; set; } 32 | } 33 | 34 | public class StatementResult 35 | { 36 | public string Key { get; set; } 37 | public string Name { get; set; } 38 | public int? AccountNumber { get; set; } 39 | public string Currency { get; set; } 40 | public string StartDate { get; set; } 41 | public string EndDate { get; set; } 42 | public decimal? OpeningBalance { get; set; } 43 | public decimal? ClosingBalance { get; set; } 44 | public IEnumerable TransactionDetails { get; set; } 45 | public string Message { get; set; } 46 | } 47 | 48 | public class AccountTransaction 49 | { 50 | public string Date { get; set; } 51 | public string TransactionDetail { get; set; } 52 | public string Withdrawal { get; set; } 53 | public string Deposit { get; set; } 54 | public decimal Balance { get; set; } 55 | } 56 | 57 | public class SecurityToken 58 | { 59 | public string auth_token { get; set; } 60 | } 61 | 62 | public class Login 63 | { 64 | public string UserName { get; set; } 65 | public string Password { get; set; } 66 | } 67 | 68 | public enum TransactionType 69 | { 70 | Balance =0, 71 | Deposit = 1, 72 | Withdrawal = 2 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Processors/TriggerProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Processors 2 | { 3 | using Microsoft.Extensions.Options; 4 | using Publisher.Framework.Commands; 5 | using Publisher.Framework.Configurations; 6 | using Publisher.Framework.Messages; 7 | using Receiver.Service.Clients; 8 | using System; 9 | using System.Threading.Tasks; 10 | 11 | public class TriggerProcessor : IMessageProcessor 12 | { 13 | private readonly IIdentityClient _identityClient; 14 | private readonly ICommandPublisher _commandPublisher; 15 | private readonly QueueNames _queueNames; 16 | 17 | public TriggerProcessor(ICommandPublisher commandPublisher, IOptions options, IIdentityClient identityClient) 18 | { 19 | if (options == null) 20 | { 21 | throw new ArgumentNullException(nameof(options)); 22 | } 23 | 24 | _commandPublisher = commandPublisher ?? throw new ArgumentNullException(nameof(commandPublisher)); 25 | _identityClient = identityClient ?? throw new ArgumentNullException(nameof(identityClient)); 26 | 27 | _queueNames = options.Value; 28 | } 29 | 30 | public async Task ProcessMessageAsync(ICommand command) 31 | { 32 | var qMessage = (TriggerMessage)command; 33 | 34 | if(qMessage.AccountNumbers == null) 35 | { 36 | // get the list of user accounts 37 | var userAccounts = await _identityClient.GetUserAccounts(); 38 | 39 | // publish the message in to the queue for each user 40 | foreach (var userAccount in userAccounts) 41 | { 42 | var statementMessage = new StatementMessage() { Name = userAccount.Name, AccountNumber = userAccount.AccountNumber, Month = qMessage.Month, Currency = userAccount.Currency }; 43 | await _commandPublisher.PublishAsync(_queueNames.Statement, statementMessage); 44 | } 45 | } 46 | else 47 | { 48 | foreach (var accountNumber in qMessage.AccountNumbers) 49 | { 50 | var userAccount = await _identityClient.GetUserAccount(accountNumber); 51 | var statementMessage = new StatementMessage() { Name = userAccount.Name, AccountNumber = userAccount.AccountNumber, Month = qMessage.Month, Currency = userAccount.Currency }; 52 | await _commandPublisher.PublishAsync(_queueNames.Statement, statementMessage); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Types/Money.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Types 2 | { 3 | using Transaction.Framework.Exceptions; 4 | 5 | public struct Money 6 | { 7 | public Money(decimal amount, Currency currency) 8 | { 9 | Amount = amount; 10 | Currency = currency; 11 | } 12 | 13 | public decimal Amount { get; private set; } 14 | public Currency Currency { get; private set; } 15 | 16 | public Money ToAmount(decimal amount) => new Money(amount, Currency); 17 | 18 | public static Money operator +(decimal d, Money m) 19 | { 20 | return new Money(m.Amount + d, m.Currency); 21 | } 22 | 23 | public static Money operator +(Money m, decimal d) 24 | { 25 | return d + m; 26 | } 27 | 28 | public static Money operator -(Money m, decimal d) 29 | { 30 | return new Money(m.Amount - d, m.Currency); 31 | } 32 | 33 | public static bool operator >(Money m, decimal d) 34 | { 35 | return m.Amount > d; 36 | } 37 | 38 | public static bool operator <(Money m, decimal d) 39 | { 40 | return m.Amount < d; 41 | } 42 | 43 | public static bool operator >=(Money m, decimal d) 44 | { 45 | return m.Amount >= d; 46 | } 47 | 48 | public static bool operator <=(Money m, decimal d) 49 | { 50 | return m.Amount <= d; 51 | } 52 | 53 | public static Money operator +(Money m1, Money m2) 54 | { 55 | RequireSameCurrency(m1, m2); 56 | return m1 + m2.Amount; 57 | } 58 | 59 | public static Money operator -(Money m1, Money m2) 60 | { 61 | RequireSameCurrency(m1, m2); 62 | return m1 - m2.Amount; 63 | } 64 | 65 | public static bool operator >(Money m1, Money m2) 66 | { 67 | RequireSameCurrency(m1, m2); 68 | return m1.Amount > m2.Amount; 69 | } 70 | 71 | public static bool operator <(Money m1, Money m2) 72 | { 73 | RequireSameCurrency(m1, m2); 74 | return m1.Amount < m2.Amount; 75 | } 76 | 77 | public static bool operator >=(Money m1, Money m2) 78 | { 79 | RequireSameCurrency(m1, m2); 80 | return m1.Amount >= m2.Amount; 81 | } 82 | 83 | public static bool operator <=(Money m1, Money m2) 84 | { 85 | RequireSameCurrency(m1, m2); 86 | return m1.Amount <= m2.Amount; 87 | } 88 | 89 | private static void RequireSameCurrency(Money m1, Money m2) 90 | { 91 | if (!m1.Currency.Equals(m2.Currency)) 92 | throw new CurrencyMismatchException(m1.Currency, m2.Currency); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Services/Statement/Statement.WebApi/Middlewares/ExceptionHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Statement.WebApi.Middlewares 2 | { 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | using Statement.WebApi.Models; 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | public class ExceptionHandlerMiddleware : IMiddleware 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public ExceptionHandlerMiddleware(ILogger logger) 15 | { 16 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 17 | } 18 | 19 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 20 | { 21 | try 22 | { 23 | await next(context); 24 | } 25 | catch (Exception ex) 26 | { 27 | var message = CreateMessage(context, ex); 28 | _logger.LogError(message, ex); 29 | 30 | await HandleExceptionAsync(context, ex); 31 | } 32 | } 33 | 34 | private async Task HandleExceptionAsync(HttpContext context, Exception e) 35 | { 36 | var result = new ResultModel() { IsSuccessful = false, Message = e.Message }; 37 | int statusCode; 38 | 39 | if (e is ArgumentException || e is ArgumentNullException) 40 | { 41 | statusCode = StatusCodes.Status400BadRequest; 42 | } 43 | else if (e is Statement.Framework.Exceptions.StatementException) 44 | { 45 | statusCode = StatusCodes.Status422UnprocessableEntity; 46 | } 47 | else 48 | { 49 | statusCode = StatusCodes.Status500InternalServerError; 50 | result.Message = "Unknown error, please contact the system admin"; 51 | } 52 | 53 | _logger.LogError(e, e.Message); 54 | 55 | var response = JsonConvert.SerializeObject(result, Formatting.Indented, 56 | new JsonSerializerSettings 57 | { 58 | NullValueHandling = NullValueHandling.Ignore 59 | }); 60 | 61 | context.Response.StatusCode = statusCode; 62 | await context.Response.WriteAsync(response); 63 | } 64 | 65 | private string CreateMessage(HttpContext context, Exception e) 66 | { 67 | var message = $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; 68 | 69 | if (e.InnerException != null) 70 | { 71 | message = $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; 72 | } 73 | 74 | return $"{message} RequestId: {context.TraceIdentifier}"; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Services/Transaction/Middlewares/ExceptionHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.WebApi.Middlewares 2 | { 3 | using Transaction.WebApi.Models; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | public class ExceptionHandlerMiddleware : IMiddleware 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public ExceptionHandlerMiddleware(ILogger logger) 15 | { 16 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 17 | } 18 | 19 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 20 | { 21 | try 22 | { 23 | await next(context); 24 | } 25 | catch (Exception ex) 26 | { 27 | var message = CreateMessage(context, ex); 28 | _logger.LogError(message, ex); 29 | 30 | await HandleExceptionAsync(context, ex); 31 | } 32 | } 33 | 34 | private async Task HandleExceptionAsync(HttpContext context, Exception e) 35 | { 36 | var result = new TransactionResultModel() { IsSuccessful = false, Message = e.Message }; 37 | int statusCode; 38 | 39 | if (e is ArgumentException || e is ArgumentNullException) 40 | { 41 | statusCode = StatusCodes.Status400BadRequest; 42 | } 43 | else if (e is Transaction.Framework.Exceptions.TransactionException) 44 | { 45 | statusCode = StatusCodes.Status422UnprocessableEntity; 46 | } 47 | else 48 | { 49 | statusCode = StatusCodes.Status500InternalServerError; 50 | result.Message = "Unknown error, please contact the system admin"; 51 | } 52 | 53 | _logger.LogError(e, e.Message); 54 | 55 | var response = JsonConvert.SerializeObject(result, Formatting.Indented, 56 | new JsonSerializerSettings 57 | { 58 | NullValueHandling = NullValueHandling.Ignore 59 | }); 60 | 61 | context.Response.StatusCode = statusCode; 62 | await context.Response.WriteAsync(response); 63 | } 64 | 65 | private string CreateMessage(HttpContext context, Exception e) 66 | { 67 | var message = $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; 68 | 69 | if (e.InnerException != null) 70 | { 71 | message = $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; 72 | } 73 | 74 | return $"{message} RequestId: {context.TraceIdentifier}"; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Gateway/Gateway.WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace Gateway.WebApi 2 | { 3 | using Microsoft.AspNetCore.Authentication.JwtBearer; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.IdentityModel.Tokens; 10 | using Ocelot.DependencyInjection; 11 | using Ocelot.Middleware; 12 | using System.Text; 13 | 14 | public class Startup 15 | { 16 | public Startup(IHostingEnvironment env) 17 | { 18 | var builder = new ConfigurationBuilder() 19 | .SetBasePath(env.ContentRootPath) 20 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 21 | .AddJsonFile("configuration.json") 22 | .AddEnvironmentVariables(); 23 | 24 | Configuration = builder.Build(); 25 | } 26 | 27 | public IConfiguration Configuration { get; } 28 | 29 | // This method gets called by the runtime. Use this method to add services to the container. 30 | public void ConfigureServices(IServiceCollection services) 31 | { 32 | services.AddOcelot(Configuration); 33 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 34 | 35 | var appSettingSection = Configuration.GetSection("AppSettings"); 36 | services.Configure(appSettingSection); 37 | 38 | var appSettings = appSettingSection.Get(); 39 | var key = Encoding.ASCII.GetBytes(appSettings.Secret); 40 | 41 | services.AddAuthentication(x => 42 | { 43 | x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 44 | x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 45 | }) 46 | .AddJwtBearer(x => 47 | { 48 | x.RequireHttpsMetadata = false; 49 | x.SaveToken = true; 50 | x.TokenValidationParameters = new TokenValidationParameters 51 | { 52 | ValidateIssuerSigningKey = true, 53 | IssuerSigningKey = new SymmetricSecurityKey(key), 54 | ValidateIssuer = false, 55 | ValidateAudience = false 56 | }; 57 | }); 58 | } 59 | 60 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 61 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 62 | { 63 | if (env.IsDevelopment()) 64 | { 65 | app.UseDeveloperExceptionPage(); 66 | } 67 | 68 | app.UseAuthentication(); 69 | app.UseOcelot().Wait(); 70 | app.UseMvc(); 71 | } 72 | } 73 | 74 | public class AppSettings 75 | { 76 | public string Secret { get; set; } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Processors/StatementProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Processors 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Publisher.Framework.Commands; 5 | using Publisher.Framework.Messages; 6 | using Receiver.Service.Documents; 7 | using Receiver.Service.Helpers; 8 | using Receiver.Service.Types; 9 | using System; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | public class StatementProcessor : BaseProcessor, IMessageProcessor 14 | { 15 | private ILogger _logger; 16 | 17 | public StatementProcessor(IServiceProvider serviceProvider, ILogger logger, IRetryHelper retryHelper) : base(serviceProvider, logger, retryHelper) 18 | { 19 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 20 | } 21 | 22 | protected override async Task ProcessAsync(ICommand command) 23 | { 24 | var qMessage = (StatementMessage)command; 25 | 26 | var monthlyStatement = await _transactionClient.GetStatement(qMessage.AccountNumber, qMessage.Month); 27 | 28 | var statementDate = new StatementDate(qMessage.Month); 29 | 30 | const string DateFormat = "dd/MM/yyyy"; 31 | 32 | var accountStatement = new AccountStatement() 33 | { 34 | Key = $"{monthlyStatement.AccountNumber}-{qMessage.Month}", 35 | Name = qMessage.Name, 36 | AccountNumber = monthlyStatement.AccountNumber, 37 | Currency = qMessage.Currency, 38 | StartDate = statementDate.StartDate.ToString(DateFormat), 39 | EndDate = statementDate.EndDate.ToString(DateFormat), 40 | // Logic used here to calculate opening & closing balance is not accurate and needs improvement 41 | OpeningBalance = monthlyStatement.TransactionDetails.OrderBy(i => i.Date).Select(i => i.CurrentBalance).FirstOrDefault(), 42 | ClosingBalance = monthlyStatement.TransactionDetails.OrderByDescending(i => i.Date).Select(i => 43 | i.TransactionType == TransactionType.Deposit.ToString() ? 44 | (i.CurrentBalance + i.Amount) : 45 | (i.CurrentBalance - i.Amount) 46 | ).FirstOrDefault(), 47 | TransactionDetails = monthlyStatement.TransactionDetails.Select(i => new AccountTransaction() 48 | { 49 | Date = i.Date.ToString(DateFormat), 50 | TransactionDetail = i.Description, 51 | Withdrawal = i.TransactionType == TransactionType.Withdrawal.ToString() ? i.Amount.ToString() : string.Empty, 52 | Deposit = i.TransactionType == TransactionType.Deposit.ToString() ? i.Amount.ToString() : string.Empty, 53 | Balance = i.TransactionType == TransactionType.Deposit.ToString() ? 54 | (i.CurrentBalance + i.Amount) : 55 | (i.CurrentBalance - i.Amount) 56 | }) 57 | }; 58 | 59 | return accountStatement; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/Framework/UnitTest/Data/Repositories/AccountSummaryRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using Transaction.Framework.Data; 5 | using Transaction.Framework.Data.Entities; 6 | using Transaction.Framework.Data.Repositories; 7 | using Transaction.Framework.Mappers; 8 | using Xunit; 9 | 10 | namespace Transaction.Framework.UnitTest.Data.Repositories 11 | { 12 | public class AccountSummaryRepositoryTest 13 | { 14 | protected AccountSummaryRepository AccountSummaryRepositoryUnderTest { get; set; } 15 | protected ApplicationDbContext DbContextInMemory { get; } 16 | protected MapperConfiguration MappingConfig { get; } 17 | protected IMapper Mapper { get; } 18 | 19 | public AccountSummaryRepositoryTest() 20 | { 21 | DbContextInMemory = GetInMemoryDbContext(); 22 | MappingConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile()); }); 23 | Mapper = MappingConfig.CreateMapper(); 24 | AccountSummaryRepositoryUnderTest = new AccountSummaryRepository(DbContextInMemory); 25 | } 26 | 27 | public class Read : AccountSummaryRepositoryTest 28 | { 29 | [Fact] 30 | public void Should_return_accountsummary_when_accountnumber_exist() 31 | { 32 | // Arrange 33 | int existingAccountNumber = 2398; 34 | 35 | // Act 36 | var result = AccountSummaryRepositoryUnderTest.Read(existingAccountNumber).Result; 37 | 38 | // Assert 39 | Assert.Equal(AccountSummaryDataEntity.AccountNumber, result.AccountNumber); 40 | Assert.Equal(AccountSummaryDataEntity.Balance, result.Balance); 41 | Assert.Equal(AccountSummaryDataEntity.Currency, result.Currency); 42 | } 43 | 44 | [Fact] 45 | public void Should_return_null_when_accountnumber_doesnotexist() 46 | { 47 | // Arrange 48 | int invalidAccountNumber = 23981; 49 | AccountSummaryEntity expectedResult = null; 50 | 51 | // Act 52 | var actualResult = AccountSummaryRepositoryUnderTest.Read(invalidAccountNumber).Result; 53 | 54 | // Assert 55 | Assert.Equal(expectedResult, actualResult); 56 | } 57 | } 58 | 59 | private static ApplicationDbContext GetInMemoryDbContext() 60 | { 61 | var options = new DbContextOptionsBuilder() 62 | .UseInMemoryDatabase("simpletransactiondb") 63 | .Options; 64 | var context = new ApplicationDbContext(options); 65 | 66 | context.Database.EnsureDeleted(); 67 | context.Database.EnsureCreated(); 68 | 69 | var accountSummaryDataEntity = AccountSummaryDataEntity; 70 | context.Add(accountSummaryDataEntity); 71 | context.SaveChanges(); 72 | 73 | return context; 74 | } 75 | 76 | protected static AccountSummaryEntity AccountSummaryDataEntity => new AccountSummaryEntity() 77 | { 78 | AccountNumber = 2398, 79 | Balance = 10000, 80 | Currency = "INR" 81 | }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Processors/BaseProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Receiver.Service.Processors 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using MongoDB.Driver; 6 | using Publisher.Framework.Commands; 7 | using Receiver.Service.Clients; 8 | using Receiver.Service.Documents; 9 | using Receiver.Service.Helpers; 10 | using Receiver.Service.Repository; 11 | using System; 12 | using System.Threading.Tasks; 13 | 14 | public abstract class BaseProcessor : IMessageProcessor 15 | { 16 | protected abstract Task ProcessAsync(ICommand command); 17 | 18 | protected readonly IIdentityClient _identityClient; 19 | protected readonly ITransactionClient _transactionClient; 20 | protected readonly IDocumentRepository _documentRepository; 21 | 22 | private readonly ICacheRepository _cacheRepository; 23 | private readonly int _cacheExpiryInSeconds; 24 | private readonly ILogger _logger; 25 | private readonly IRetryHelper _retryHelper; 26 | 27 | public BaseProcessor(IServiceProvider serviceProvider, ILogger logger, IRetryHelper retryHelper) 28 | { 29 | _identityClient = (IIdentityClient)serviceProvider.GetService(typeof(IIdentityClient)); 30 | _transactionClient = (ITransactionClient)serviceProvider.GetService(typeof(ITransactionClient)); 31 | _cacheRepository = (ICacheRepository)serviceProvider.GetService(typeof(ICacheRepository)); 32 | _documentRepository = (IDocumentRepository)serviceProvider.GetService(typeof(IDocumentRepository)); 33 | _cacheExpiryInSeconds = ((IOptions)serviceProvider.GetService(typeof(IOptions))).Value.ExpiryInSeconds; 34 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 35 | _retryHelper = retryHelper ?? throw new ArgumentNullException(nameof(retryHelper)); 36 | } 37 | 38 | public async Task ProcessMessageAsync(ICommand command) 39 | { 40 | var document = await ProcessAsync(command); 41 | 42 | if (document != null) 43 | { 44 | try 45 | { 46 | await UpdateDocument(document); 47 | } 48 | catch (MongoException ex) 49 | { 50 | _logger.LogError("BaseProcessor", ex, "qMessage processing failed due to mongoDb exception"); 51 | 52 | try 53 | { 54 | await _retryHelper.Do(UpdateDocument, document, new TimeSpan(0, 0, 10), 10); 55 | } 56 | catch (Exception e) 57 | { 58 | // throwing the exception pushes the message in to error queue 59 | throw e; 60 | } 61 | } 62 | } 63 | } 64 | 65 | private async Task UpdateDocument(AccountStatement document) 66 | { 67 | await _documentRepository.UpdateAsync(document.Key, document); 68 | 69 | if (_cacheRepository.KeyExistsAsync(document.Key).Result) 70 | { 71 | await _cacheRepository.SetAsync(document.Key, document, TimeSpan.FromSeconds(_cacheExpiryInSeconds)); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Services/Identity/Services/UserService.cs: -------------------------------------------------------------------------------- 1 | namespace Identity.WebApi.Services 2 | { 3 | using Identity.WebApi.Helpers; 4 | using Identity.WebApi.Models; 5 | using Microsoft.Extensions.Options; 6 | using Microsoft.IdentityModel.Tokens; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IdentityModel.Tokens.Jwt; 10 | using System.Linq; 11 | using System.Security.Claims; 12 | using System.Text; 13 | 14 | public interface IUserService 15 | { 16 | Models.SecurityToken Authenticate(string username, string password); 17 | IEnumerable GetAccountNumbers(); 18 | IEnumerable GetUserAccounts(); 19 | UserAccount GetUserAccount(int accountNumber); 20 | } 21 | 22 | public class UserService : IUserService 23 | { 24 | private List _users = new List 25 | { 26 | new User { AccountNumber = 3628101, Currency = "EUR", FullName = "Simon Peter", Username = "speter", Password = "test@123" }, 27 | new User { AccountNumber = 3637897, Currency = "EUR", FullName = "Glen Woodhouse", Username = "gwoodhouse", Password = "pass@123" }, 28 | new User { AccountNumber = 3648755, Currency = "EUR", FullName = "John Smith", Username = "jsmith", Password = "admin@123" }, 29 | }; 30 | 31 | private readonly AppSettings _appSettings; 32 | 33 | public UserService(IOptions appSettings) 34 | { 35 | _appSettings = appSettings.Value; 36 | } 37 | 38 | public Models.SecurityToken Authenticate(string username, string password) 39 | { 40 | var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password); 41 | 42 | // return null if user not found 43 | if (user == null) 44 | return null; 45 | 46 | // authentication successful so generate jwt token 47 | var tokenHandler = new JwtSecurityTokenHandler(); 48 | var key = Encoding.ASCII.GetBytes(_appSettings.Secret); 49 | var tokenDescriptor = new SecurityTokenDescriptor 50 | { 51 | Subject = new ClaimsIdentity(new Claim[] 52 | { 53 | new Claim("accountnumber", user.AccountNumber.ToString()), 54 | new Claim("currency", user.Currency), 55 | new Claim("name", user.FullName) 56 | }), 57 | Expires = DateTime.UtcNow.AddDays(7), 58 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) 59 | }; 60 | var token = tokenHandler.CreateToken(tokenDescriptor); 61 | var jwtSecurityToken = tokenHandler.WriteToken(token); 62 | 63 | return new Models.SecurityToken() { auth_token = jwtSecurityToken }; 64 | } 65 | 66 | public IEnumerable GetAccountNumbers() 67 | { 68 | return _users.Select(i => i.AccountNumber); 69 | } 70 | 71 | public IEnumerable GetUserAccounts() 72 | { 73 | return _users.Select(i => new UserAccount() { Name = i.FullName, AccountNumber = i.AccountNumber, Currency = i.Currency }); 74 | } 75 | 76 | public UserAccount GetUserAccount(int accountNumber) 77 | { 78 | return _users 79 | .Select(i => new UserAccount() { Name = i.FullName, AccountNumber = i.AccountNumber, Currency = i.Currency }) 80 | .FirstOrDefault(i => i.AccountNumber == accountNumber); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/Framework/UnitTest/Data/Repositories/AccountTransactionRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using Transaction.Framework.Data; 5 | using Transaction.Framework.Data.Entities; 6 | using Transaction.Framework.Data.Repositories; 7 | using Transaction.Framework.Mappers; 8 | using Transaction.Framework.Types; 9 | using Xunit; 10 | 11 | namespace Transaction.Framework.UnitTest.Data.Repositories 12 | { 13 | public class AccountTransactionRepositoryTest 14 | { 15 | protected AccountTransactionRepository AccountTransactionRepositoryUnderTest { get; set; } 16 | protected ApplicationDbContext DbContextInMemory { get; } 17 | protected MapperConfiguration MappingConfig { get; } 18 | protected IMapper Mapper { get; } 19 | 20 | public AccountTransactionRepositoryTest() 21 | { 22 | DbContextInMemory = GetInMemoryDbContext(); 23 | MappingConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile()); }); 24 | Mapper = MappingConfig.CreateMapper(); 25 | AccountTransactionRepositoryUnderTest = new AccountTransactionRepository(DbContextInMemory); 26 | } 27 | 28 | public class Create : AccountTransactionRepositoryTest 29 | { 30 | // [Fact] 31 | public void Should_create_transaction_and_update_summary() 32 | { 33 | // Arrange 34 | var accountTransaction = CreatedAccountTransactionDepositDataEntity; 35 | var accountSummary = UpdatedAccountSummaryDataEntity; 36 | 37 | // Act 38 | var result = AccountTransactionRepositoryUnderTest.Create(accountTransaction, accountSummary); 39 | 40 | // Assert 41 | Assert.True(true); 42 | } 43 | } 44 | 45 | private static ApplicationDbContext GetInMemoryDbContext() 46 | { 47 | var options = new DbContextOptionsBuilder() 48 | .UseInMemoryDatabase("simpletransactiondb") 49 | .Options; 50 | var context = new ApplicationDbContext(options); 51 | 52 | context.Database.EnsureDeleted(); 53 | context.Database.EnsureCreated(); 54 | 55 | var accountSummaryDataEntity = new AccountSummaryEntity() 56 | { 57 | AccountNumber = 2398, 58 | Balance = 10000, 59 | Currency = "INR" 60 | }; 61 | context.Add(accountSummaryDataEntity); 62 | context.SaveChanges(); 63 | 64 | return context; 65 | } 66 | 67 | protected static AccountSummaryEntity UpdatedAccountSummaryDataEntity => 68 | new AccountSummaryEntity() 69 | { 70 | AccountNumber = 2398, 71 | Balance = 15000, 72 | Currency = "INR" 73 | }; 74 | 75 | protected static AccountTransactionEntity CreatedAccountTransactionDepositDataEntity => 76 | new AccountTransactionEntity() { 77 | AccountNumber = 2398, 78 | Date = DateTime.UtcNow, 79 | Description = "Credit", 80 | Amount = 5000, 81 | TransactionType = TransactionType.Deposit.ToString() 82 | }; 83 | 84 | protected static AccountTransactionEntity AccountTransactionWithdrawalDataEntity => 85 | new AccountTransactionEntity() 86 | { 87 | AccountNumber = 2398, 88 | Date = DateTime.UtcNow, 89 | Description = "Debit", 90 | Amount = 2500, 91 | TransactionType = TransactionType.Withdrawal.ToString() 92 | }; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Database/SimpleTransaction/SimpleTransaction.sqlproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | SimpleTransaction 8 | 2.0 9 | 4.1 10 | {018f80d5-e3f4-4243-9f7c-f965d929fffc} 11 | Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider 12 | Database 13 | 14 | 15 | SimpleTransaction 16 | SimpleTransaction 17 | 1033, CI 18 | BySchemaAndSchemaType 19 | True 20 | v4.5 21 | CS 22 | Properties 23 | False 24 | True 25 | True 26 | 27 | 28 | bin\Release\ 29 | $(MSBuildProjectName).sql 30 | False 31 | pdbonly 32 | true 33 | false 34 | true 35 | prompt 36 | 4 37 | 38 | 39 | bin\Debug\ 40 | $(MSBuildProjectName).sql 41 | false 42 | true 43 | full 44 | false 45 | true 46 | true 47 | prompt 48 | 4 49 | 50 | 51 | 11.0 52 | 53 | True 54 | 11.0 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Data/Repositories/AccountTransactionRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Data.Repositories 2 | { 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Transaction.Framework.Data.Entities; 9 | using Transaction.Framework.Data.Interface; 10 | using Transaction.Framework.Exceptions; 11 | using Transaction.Framework.Types; 12 | 13 | public class AccountTransactionRepository : IAccountTransactionRepository 14 | { 15 | private readonly ApplicationDbContext _dbContext; 16 | private readonly DbSet _accountSummaryEntity; 17 | private readonly DbSet _accountTransactionEntity; 18 | 19 | public AccountTransactionRepository(ApplicationDbContext dbContext) 20 | { 21 | _dbContext = dbContext; 22 | _accountSummaryEntity = _dbContext.Set(); 23 | _accountTransactionEntity = _dbContext.Set(); 24 | } 25 | 26 | public async Task Create(AccountTransactionEntity accountTransactionEntity, AccountSummaryEntity accountSummaryEntity) 27 | { 28 | _accountTransactionEntity.Add(accountTransactionEntity); 29 | _accountSummaryEntity.Update(accountSummaryEntity); 30 | 31 | bool isSaved = false; 32 | 33 | while (!isSaved) 34 | { 35 | try 36 | { 37 | await _dbContext.SaveChangesAsync(); 38 | isSaved = true; 39 | } 40 | catch (DbUpdateConcurrencyException ex) 41 | { 42 | foreach (var entry in ex.Entries) 43 | { 44 | if (entry.Entity is AccountSummaryEntity) 45 | { 46 | var databaseValues = entry.GetDatabaseValues(); 47 | 48 | if (databaseValues != null) 49 | { 50 | entry.OriginalValues.SetValues(databaseValues); 51 | CalculateNewBalance(); 52 | 53 | void CalculateNewBalance() 54 | { 55 | var balance = (decimal)entry.OriginalValues["Balance"]; 56 | var amount = accountTransactionEntity.Amount; 57 | 58 | if (accountTransactionEntity.TransactionType == TransactionType.Deposit.ToString()) 59 | { 60 | accountSummaryEntity.Balance = 61 | balance += amount; 62 | } 63 | else if (accountTransactionEntity.TransactionType == TransactionType.Withdrawal.ToString()) 64 | { 65 | if(amount > balance) 66 | throw new InsufficientBalanceException(); 67 | 68 | accountSummaryEntity.Balance = 69 | balance -= amount; 70 | } 71 | } 72 | } 73 | else 74 | { 75 | throw new NotSupportedException(); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | public async Task> Get(int accountNumber, DateTime startDate, DateTime endDate) 84 | { 85 | return await _accountTransactionEntity.Where(i => i.AccountNumber == accountNumber && i.Date >= startDate && i.Date <= endDate).ToListAsync(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Repository/DocumentRepository.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Driver; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Receiver.Service.Repository 9 | { 10 | public class DocumentRepository : IDocumentRepository where TDocument : class 11 | { 12 | private IMongoCollection _collection; 13 | 14 | public DocumentRepository(IMongoCollection collection) 15 | { 16 | _collection = collection; 17 | } 18 | 19 | public async Task InsertAsync(TDocument document) 20 | { 21 | await _collection.InsertOneAsync(document); 22 | } 23 | 24 | public async Task GetAsync(TKey id) 25 | { 26 | var filter = Builders.Filter.Eq("_id", id); 27 | var result = await _collection.Find(filter).FirstOrDefaultAsync(); 28 | 29 | return result; 30 | } 31 | 32 | public async Task> GetAllAsync() 33 | { 34 | return await _collection.Find(new BsonDocument()).ToListAsync(); 35 | } 36 | 37 | public async Task> GetAsync(string field, TValue value) 38 | { 39 | var filter = Builders.Filter.Eq(field, value); 40 | var result = await _collection.Find(filter).ToListAsync(); 41 | 42 | return result; 43 | } 44 | 45 | public async Task> GetAsync(int startingFrom, int count) 46 | { 47 | var result = await _collection.Find(new BsonDocument()) 48 | .Skip(startingFrom) 49 | .Limit(count) 50 | .ToListAsync(); 51 | 52 | return result; 53 | } 54 | 55 | public async Task UpdateAsync(TKey id, string field, TValue value) 56 | { 57 | var filter = Builders.Filter.Eq("_id", id); 58 | var update = Builders.Update.Set(field, value); 59 | var result = await _collection.UpdateOneAsync(filter, update); 60 | return result.ModifiedCount != 0; 61 | } 62 | 63 | public async Task UpdateAsync(TKey id, TDocument document) 64 | { 65 | var filter = Builders.Filter.Eq("_id", id); 66 | var result = await _collection.ReplaceOneAsync(filter, document, new UpdateOptions() { IsUpsert = true }); 67 | return result.IsAcknowledged; 68 | } 69 | 70 | public async Task DeleteAsync(TKey id) 71 | { 72 | var filter = Builders.Filter.Eq("_id", id); 73 | var result = await _collection.DeleteOneAsync(filter); 74 | return result.DeletedCount != 0; 75 | } 76 | 77 | public async Task DeleteAsync(string field, TValue value) 78 | { 79 | var filter = Builders.Filter.Eq(field, value); 80 | var result = await _collection.DeleteOneAsync(filter); 81 | return result.IsAcknowledged; 82 | } 83 | 84 | public async Task DeleteAllAsync() 85 | { 86 | var filter = new BsonDocument(); 87 | var result = await _collection.DeleteManyAsync(filter); 88 | return result.DeletedCount; 89 | } 90 | 91 | public async Task DropAsync() 92 | { 93 | await _collection.Database.DropCollectionAsync(typeof(TDocument).Name.ToLower()); 94 | } 95 | 96 | public async Task CreateAsync() 97 | { 98 | await _collection.Database.CreateCollectionAsync(typeof(TDocument).Name.ToLower()); 99 | } 100 | 101 | public async Task AnyAsync(string field, TValue value) 102 | { 103 | var filter = Builders.Filter.Eq(field, value); 104 | return await _collection.Find(filter).AnyAsync(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/App/Console/Extension/TableParserExtension.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleBanking.ConsoleApp.Extension 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Reflection; 9 | using System.Text; 10 | 11 | public static class TableParserExtensions 12 | { 13 | public static string ToStringTable(this IEnumerable values, string[] columnHeaders, params Func[] valueSelectors) 14 | { 15 | return ToStringTable(values.ToArray(), columnHeaders, valueSelectors); 16 | } 17 | 18 | public static string ToStringTable(this T[] values, string[] columnHeaders, params Func[] valueSelectors) 19 | { 20 | Debug.Assert(columnHeaders.Length == valueSelectors.Length); 21 | 22 | var arrValues = new string[values.Length + 1, valueSelectors.Length]; 23 | 24 | // Fill headers 25 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 26 | { 27 | arrValues[0, colIndex] = columnHeaders[colIndex]; 28 | } 29 | 30 | // Fill table rows 31 | for (int rowIndex = 1; rowIndex < arrValues.GetLength(0); rowIndex++) 32 | { 33 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 34 | { 35 | object value = valueSelectors[colIndex].Invoke(values[rowIndex - 1]); 36 | 37 | arrValues[rowIndex, colIndex] = value != null ? value.ToString() : "null"; 38 | } 39 | } 40 | 41 | return ToStringTable(arrValues); 42 | } 43 | 44 | public static string ToStringTable(this string[,] arrValues) 45 | { 46 | int[] maxColumnsWidth = GetMaxColumnsWidth(arrValues); 47 | var headerSpliter = new string('-', maxColumnsWidth.Sum(i => i + 3) - 1); 48 | 49 | var sb = new StringBuilder(); 50 | for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++) 51 | { 52 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 53 | { 54 | // Print cell 55 | string cell = arrValues[rowIndex, colIndex]; 56 | cell = cell.PadRight(maxColumnsWidth[colIndex]); 57 | sb.Append(" | "); 58 | sb.Append(cell); 59 | } 60 | 61 | // Print end of line 62 | sb.Append(" | "); 63 | sb.AppendLine(); 64 | 65 | // Print splitter 66 | if (rowIndex == 0) 67 | { 68 | sb.AppendFormat(" |{0}| ", headerSpliter); 69 | sb.AppendLine(); 70 | } 71 | } 72 | 73 | return sb.ToString(); 74 | } 75 | 76 | private static int[] GetMaxColumnsWidth(string[,] arrValues) 77 | { 78 | var maxColumnsWidth = new int[arrValues.GetLength(1)]; 79 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 80 | { 81 | for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++) 82 | { 83 | int newLength = arrValues[rowIndex, colIndex].Length; 84 | int oldLength = maxColumnsWidth[colIndex]; 85 | 86 | if (newLength > oldLength) 87 | { 88 | maxColumnsWidth[colIndex] = newLength; 89 | } 90 | } 91 | } 92 | 93 | return maxColumnsWidth; 94 | } 95 | 96 | public static string ToStringTable(this IEnumerable values, params Expression>[] valueSelectors) 97 | { 98 | var headers = valueSelectors.Select(func => GetProperty(func).Name).ToArray(); 99 | var selectors = valueSelectors.Select(exp => exp.Compile()).ToArray(); 100 | return ToStringTable(values, headers, selectors); 101 | } 102 | 103 | private static PropertyInfo GetProperty(Expression> expresstion) 104 | { 105 | if (expresstion.Body is UnaryExpression) 106 | { 107 | if ((expresstion.Body as UnaryExpression).Operand is MemberExpression) 108 | { 109 | return ((expresstion.Body as UnaryExpression).Operand as MemberExpression).Member as PropertyInfo; 110 | } 111 | } 112 | 113 | if ((expresstion.Body is MemberExpression)) 114 | { 115 | return (expresstion.Body as MemberExpression).Member as PropertyInfo; 116 | } 117 | return null; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /test/unitTest/testReports 263 | -------------------------------------------------------------------------------- /src/Frameworks/Transaction/Services/TransactionService.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.Services 2 | { 3 | using AutoMapper; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Threading.Tasks; 7 | using Transaction.Framework.Data.Entities; 8 | using Transaction.Framework.Data.Interface; 9 | using Transaction.Framework.Domain; 10 | using Transaction.Framework.Services.Interface; 11 | using Transaction.Framework.Types; 12 | using Transaction.Framework.Extensions; 13 | using Transaction.Framework.Validation; 14 | using Newtonsoft.Json; 15 | using System.Linq; 16 | 17 | public class TransactionService : ITransactionService 18 | { 19 | private readonly IAccountSummaryRepository _accountSummaryRepository; 20 | private readonly IAccountTransactionRepository _accountTransactionRepository; 21 | private readonly IMapper _mapper; 22 | private readonly ILogger _logger; 23 | 24 | public TransactionService(IAccountSummaryRepository accountSummaryRepository, IAccountTransactionRepository accountTransactionRepository, IMapper mapper, ILogger logger) 25 | { 26 | _accountSummaryRepository = accountSummaryRepository ?? throw new ArgumentNullException(nameof(accountSummaryRepository)); 27 | _accountTransactionRepository = accountTransactionRepository ?? throw new ArgumentNullException(nameof(accountTransactionRepository)); 28 | _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); 29 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 30 | } 31 | 32 | public async Task Balance(int accountNumber) 33 | { 34 | var accountSummary = await GetAccountSummary(accountNumber); 35 | await accountSummary.Validate(accountNumber); 36 | return _mapper.Map(accountSummary); 37 | } 38 | 39 | public async Task Deposit(AccountTransaction accountTransaction) 40 | { 41 | _logger.LogInformation(LoggingEvents.Deposit, "account transaction:{0}", JsonConvert.SerializeObject(accountTransaction)); 42 | 43 | Guard.ArgumentNotNull(nameof(accountTransaction), accountTransaction); 44 | 45 | var accountNumber = accountTransaction.AccountNumber; 46 | var accountSummary = await GetAccountSummary(accountNumber); 47 | 48 | await accountSummary.Validate(accountNumber); 49 | await accountTransaction.Validate(accountSummary); 50 | 51 | var balance = accountSummary.Balance; 52 | var amount = accountTransaction.Amount; 53 | 54 | accountTransaction.TransactionType = TransactionType.Deposit; 55 | accountTransaction.CurrentBalance = balance; 56 | accountSummary.Balance = balance + amount; 57 | 58 | var transactionResult = await 59 | CreateTransactionAndUpdateSummary( 60 | accountTransaction, 61 | accountSummary 62 | ); 63 | 64 | _logger.LogInformation(LoggingEvents.Deposit, "transaction result:{0}", JsonConvert.SerializeObject(transactionResult)); 65 | 66 | return transactionResult; 67 | 68 | } 69 | 70 | public async Task Statement(int accountNumber, StatementDate statementDate) 71 | { 72 | var accountSummary = await GetAccountSummary(accountNumber); 73 | await accountSummary.Validate(accountNumber); 74 | 75 | var accountTransactionEntity = await _accountTransactionRepository 76 | .Get(accountNumber, statementDate.StartDate, statementDate.EndDate); 77 | 78 | var currency = accountSummary.Balance.Currency; 79 | 80 | var accountStatement = new AccountStatement() 81 | { 82 | AccountNumber = accountSummary.AccountNumber, 83 | Currency = currency, 84 | Date = statementDate, 85 | TransactionDetails = accountTransactionEntity.Select(txn => new StatementTransaction() { 86 | TransactionType = txn.TransactionType.TryParseEnum(), 87 | Date = txn.Date, 88 | Description = txn.Description, 89 | Amount = new Money(txn.Amount, currency), 90 | CurrentBalance = new Money(txn.CurrentBalance, currency) 91 | }) 92 | }; 93 | 94 | return accountStatement; 95 | } 96 | 97 | public async Task Withdraw(AccountTransaction accountTransaction) 98 | { 99 | _logger.LogInformation(LoggingEvents.Withdrawal, "account transaction:{0}", JsonConvert.SerializeObject(accountTransaction)); 100 | 101 | Guard.ArgumentNotNull(nameof(accountTransaction), accountTransaction); 102 | 103 | var accountNumber = accountTransaction.AccountNumber; 104 | var accountSummary = await GetAccountSummary(accountNumber); 105 | 106 | await accountSummary.Validate(accountNumber); 107 | await accountTransaction.Validate(accountSummary); 108 | 109 | var balance = accountSummary.Balance; 110 | var amount = accountTransaction.Amount; 111 | 112 | accountTransaction.TransactionType = TransactionType.Withdrawal; 113 | accountTransaction.CurrentBalance = balance; 114 | accountSummary.Balance = balance - amount; 115 | 116 | var transactionResult = await CreateTransactionAndUpdateSummary( 117 | accountTransaction, accountSummary); 118 | 119 | _logger.LogInformation(LoggingEvents.Withdrawal, "transaction result:{0}", JsonConvert.SerializeObject(transactionResult)); 120 | 121 | return transactionResult; 122 | } 123 | 124 | #region private helpers 125 | 126 | private async Task CreateTransactionAndUpdateSummary(AccountTransaction accountTransaction, AccountSummary accountSummary) 127 | { 128 | var accountTransactionEntity = _mapper.Map(accountTransaction); 129 | var accountSummaryEntity = _mapper.Map(accountSummary); 130 | 131 | await _accountTransactionRepository.Create(accountTransactionEntity, accountSummaryEntity); 132 | var currentSummary = await _accountSummaryRepository.Read(accountTransaction.AccountNumber); 133 | 134 | var result = _mapper.Map(accountTransactionEntity); 135 | 136 | result.Balance = new Money(currentSummary.Balance, currentSummary.Currency.TryParseEnum()); 137 | return result; 138 | } 139 | 140 | private async Task GetAccountSummary(int accountNumber) 141 | { 142 | var accountSummaryEntity = await _accountSummaryRepository 143 | .Read(accountNumber); 144 | 145 | return _mapper.Map(accountSummaryEntity); 146 | } 147 | 148 | #endregion 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Background/Receiver/Receiver.Service/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using MongoDB.Driver; 7 | using Publisher.Framework.Extensions; 8 | using Receiver.Service.Clients; 9 | using Receiver.Service.Documents; 10 | using Receiver.Service.Helpers; 11 | using Receiver.Service.Processors; 12 | using Receiver.Service.Receivers; 13 | using Receiver.Service.Repository; 14 | using StackExchange.Redis; 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Net.Http.Headers; 18 | 19 | namespace Receiver.Service 20 | { 21 | public class Startup 22 | { 23 | public Startup(IConfiguration configuration) 24 | { 25 | Configuration = configuration; 26 | } 27 | 28 | public IConfiguration Configuration { get; } 29 | 30 | // This method gets called by the runtime. Use this method to add services to the container. 31 | public void ConfigureServices(IServiceCollection services) 32 | { 33 | services 34 | .AddCommandConfiguration(Configuration) 35 | .AddReceiverConfiguration() 36 | .AddProcessorConfiguration() 37 | .AddOptionsConfiguration(Configuration) 38 | .AddHttpClientConfiguration(Configuration) 39 | .AddMongoConfiguration(Configuration) 40 | .AddRedisConfiguration(Configuration); 41 | 42 | services.AddSingleton(typeof(ICacheRepository<>), typeof(CacheRepository<>)); 43 | services.AddSingleton(typeof(IDocumentRepository<>), typeof(DocumentRepository<>)); 44 | services.AddSingleton(); 45 | 46 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 47 | } 48 | 49 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 50 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 51 | { 52 | if (env.IsDevelopment()) 53 | { 54 | app.UseDeveloperExceptionPage(); 55 | } 56 | 57 | app.UseMvc(); 58 | } 59 | } 60 | 61 | internal static class ServiceExtensions 62 | { 63 | public static IServiceCollection AddReceiverConfiguration(this IServiceCollection services) 64 | { 65 | services.AddSingleton(); 66 | services.AddSingleton(); 67 | return services; 68 | } 69 | 70 | public static IServiceCollection AddCommandConfiguration(this IServiceCollection services, IConfiguration config) 71 | { 72 | // Command Config 73 | services.AddSingletonCommandPublisher(config); 74 | return services; 75 | } 76 | 77 | public static IServiceCollection AddRedisConfiguration(this IServiceCollection services, IConfiguration config) 78 | { 79 | var lazyConnection = new Lazy(() => ConnectionMultiplexer.Connect(config.GetValue("RedisCache:ConnectionString"))); 80 | IDatabase _database = lazyConnection.Value.GetDatabase(); 81 | services.AddSingleton(_database); 82 | 83 | return services; 84 | } 85 | 86 | public static IServiceCollection AddMongoConfiguration(this IServiceCollection services, IConfiguration config) 87 | { 88 | // Mongo Config 89 | var client = new MongoClient(config.GetSection("MongoConfiguration:ConnectionString").Value); 90 | var database = client.GetDatabase(config.GetSection("MongoConfiguration:Database").Value); 91 | var collection = database.GetCollection(typeof(TDocument).Name.ToLower()); 92 | 93 | services.AddScoped(_ => collection); 94 | 95 | return services; 96 | } 97 | 98 | public static IServiceCollection AddProcessorConfiguration(this IServiceCollection services) 99 | { 100 | services.AddSingleton(); 101 | services.AddSingleton(); 102 | 103 | services.AddSingleton>(key => 104 | { 105 | var serviceProvider = services.BuildServiceProvider(); 106 | 107 | switch (key) 108 | { 109 | case "trigger": 110 | return serviceProvider.GetService(); 111 | 112 | case "statement": 113 | return serviceProvider.GetService(); 114 | 115 | default: 116 | throw new KeyNotFoundException(); 117 | } 118 | }); 119 | 120 | return services; 121 | } 122 | 123 | public static IServiceCollection AddOptionsConfiguration(this IServiceCollection services, IConfiguration config) 124 | { 125 | services.Configure(options => config.GetSection("BaseUrlSettings").Bind(options)); 126 | services.Configure(options => 127 | { 128 | options.ExpiryInSeconds = int.Parse(config.GetSection("CacheExpiryInSeconds").Value); 129 | }); 130 | return services; 131 | } 132 | 133 | public static IServiceCollection AddNamedHttpClientConfiguration(this IServiceCollection services, IConfiguration configuration) 134 | { 135 | services.AddNamedHttpClient(configuration, () => new HttpConfigAttribs() 136 | { 137 | Name = NamedHttpClients.IdentityServiceClient, 138 | BaseUrl = configuration.GetSection("BaseUrlSettings")["IdentityApiBaseUrl"], 139 | MediaType = Constants.MediaTypeAppJson 140 | }); 141 | 142 | services.AddNamedHttpClient(configuration, () => new HttpConfigAttribs() 143 | { 144 | Name = NamedHttpClients.TransactionServiceClient, 145 | BaseUrl = configuration.GetSection("BaseUrlSettings")["TransactionApiBaseUrl"], 146 | MediaType = Constants.MediaTypeAppJson 147 | }); 148 | 149 | return services; 150 | } 151 | 152 | public static IServiceCollection AddNamedHttpClient(this IServiceCollection services, IConfiguration config, Func action) 153 | { 154 | var httpConfig = action(); 155 | services.AddHttpClient(httpConfig.Name, client => 156 | { 157 | client.BaseAddress = new Uri(httpConfig.BaseUrl); 158 | client.DefaultRequestHeaders.Accept.Clear(); 159 | client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(httpConfig.MediaType)); 160 | }); 161 | 162 | return services; 163 | } 164 | 165 | public static IServiceCollection AddHttpClientConfiguration(this IServiceCollection services, IConfiguration config) 166 | { 167 | services.AddNamedHttpClientConfiguration(config); 168 | services.AddSingleton(); 169 | services.AddSingleton(); 170 | return services; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /test/Framework/UnitTest/Services/TransactionServiceTest.cs: -------------------------------------------------------------------------------- 1 | namespace Transaction.Framework.UnitTest.Services 2 | { 3 | using AutoMapper; 4 | using Microsoft.Extensions.Logging; 5 | using Moq; 6 | using System.Threading.Tasks; 7 | using Transaction.Framework.Data.Entities; 8 | using Transaction.Framework.Data.Interface; 9 | using Transaction.Framework.Domain; 10 | using Transaction.Framework.Exceptions; 11 | using Transaction.Framework.Mappers; 12 | using Transaction.Framework.Services; 13 | using Xunit; 14 | 15 | public class TransactionServiceTest 16 | { 17 | protected TransactionService TransactionServiceUnderTest { get; } 18 | protected Mock AccountSummaryRepositoryMock { get; } 19 | protected Mock AccountTransactionRepositoryMock { get; } 20 | protected Mock> LoggerMock { get; } 21 | protected MapperConfiguration MappingConfig { get; } 22 | protected IMapper Mapper { get; } 23 | 24 | public TransactionServiceTest() 25 | { 26 | AccountSummaryRepositoryMock = new Mock(); 27 | AccountTransactionRepositoryMock = new Mock(); 28 | LoggerMock = new Mock>(); 29 | MappingConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile()); }); 30 | Mapper = MappingConfig.CreateMapper(); 31 | TransactionServiceUnderTest = new TransactionService(AccountSummaryRepositoryMock.Object, AccountTransactionRepositoryMock.Object, Mapper, LoggerMock.Object); 32 | } 33 | 34 | public class Balance : TransactionServiceTest 35 | { 36 | [Fact] 37 | public void Should_return_accountbalance_when_accountnumber_exist() 38 | { 39 | // Arange 40 | int accountNumber = 29489324; 41 | var accountSummary = new AccountSummaryEntity() { 42 | AccountNumber = accountNumber, 43 | Balance = 45000, 44 | Currency = "INR" 45 | }; 46 | 47 | AccountSummaryRepositoryMock 48 | .Setup(i => i.Read(It.IsAny())) 49 | .ReturnsAsync(accountSummary); 50 | 51 | // Act 52 | var result = TransactionServiceUnderTest.Balance(accountNumber).Result; 53 | 54 | // Assert 55 | Assert.Equal(accountSummary.Balance, result.Balance.Amount); 56 | } 57 | 58 | [Fact] 59 | public void Should_throw_transactionexception_when_accountnumber_notexist() 60 | { 61 | // Arange 62 | int accountNumber = 29489324; 63 | AccountSummaryEntity accountSummary = null; 64 | 65 | AccountSummaryRepositoryMock 66 | .Setup(i => i.Read(It.IsAny())) 67 | .ReturnsAsync(accountSummary); 68 | 69 | // Act 70 | var result = TransactionServiceUnderTest.Balance(accountNumber); 71 | 72 | // Assert 73 | Assert.ThrowsAsync(async () => await result); 74 | } 75 | } 76 | 77 | public class Deposit : TransactionServiceTest 78 | { 79 | [Fact] 80 | public void Should_return_transactionresult_on_successfulldeposit() 81 | { 82 | // Arrange 83 | var accountSummary = new AccountSummaryEntity() 84 | { 85 | AccountNumber = 1253667, 86 | Balance = 55000, 87 | Currency = "INR" 88 | }; 89 | 90 | var accountTransaction = new AccountTransaction() 91 | { 92 | AccountNumber = 1253667, 93 | TransactionType = Types.TransactionType.Deposit, 94 | Amount = new Types.Money(1000, Types.Currency.INR) 95 | }; 96 | 97 | AccountTransactionRepositoryMock 98 | .Setup(i => i.Create(It.IsAny(), It.IsAny())) 99 | .Returns(Task.CompletedTask); 100 | 101 | AccountSummaryRepositoryMock 102 | .Setup(i => i.Read(It.IsAny())) 103 | .ReturnsAsync(accountSummary); 104 | 105 | // Act 106 | var result = TransactionServiceUnderTest.Deposit(accountTransaction).Result; 107 | 108 | // Assert 109 | Assert.Equal(accountSummary.Balance, result.Balance.Amount); 110 | } 111 | 112 | [Fact] 113 | public void Should_throw_transactionexception__when_transactiondetails_are_invalid() 114 | { 115 | // Arrange 116 | var accountSummary = new AccountSummaryEntity() 117 | { 118 | AccountNumber = 1253667, 119 | Balance = 55000, 120 | Currency = "INR" 121 | }; 122 | 123 | var accountTransaction = new AccountTransaction() 124 | { 125 | AccountNumber = 1253667, 126 | TransactionType = Types.TransactionType.Deposit, 127 | Amount = new Types.Money(0, Types.Currency.INR) 128 | }; 129 | 130 | AccountTransactionRepositoryMock 131 | .Setup(i => i.Create(It.IsAny(), It.IsAny())) 132 | .Returns(Task.CompletedTask); 133 | 134 | AccountSummaryRepositoryMock 135 | .Setup(i => i.Read(It.IsAny())) 136 | .ReturnsAsync(accountSummary); 137 | 138 | // Act 139 | var result = TransactionServiceUnderTest.Deposit(accountTransaction); 140 | 141 | // Assert 142 | Assert.ThrowsAsync(async () => await result); 143 | } 144 | } 145 | 146 | public class Withdraw : TransactionServiceTest 147 | { 148 | [Fact] 149 | public void Should_return_transactionresult_on_successfullwithdraw() 150 | { 151 | // Arrange 152 | var accountSummary = new AccountSummaryEntity() 153 | { 154 | AccountNumber = 1253667, 155 | Balance = 55000, 156 | Currency = "INR" 157 | }; 158 | 159 | var accountTransaction = new AccountTransaction() 160 | { 161 | AccountNumber = 1253667, 162 | TransactionType = Types.TransactionType.Withdrawal, 163 | Amount = new Types.Money(1000, Types.Currency.INR) 164 | }; 165 | 166 | AccountTransactionRepositoryMock 167 | .Setup(i => i.Create(It.IsAny(), It.IsAny())) 168 | .Returns(Task.CompletedTask); 169 | 170 | AccountSummaryRepositoryMock 171 | .Setup(i => i.Read(It.IsAny())) 172 | .ReturnsAsync(accountSummary); 173 | 174 | // Act 175 | var result = TransactionServiceUnderTest.Withdraw(accountTransaction).Result; 176 | 177 | // Assert 178 | Assert.Equal(accountSummary.Balance, result.Balance.Amount); 179 | } 180 | 181 | [Fact] 182 | public void Should_throw_transactionexception__when_transactiondetails_are_invalid() 183 | { 184 | // Arrange 185 | var accountSummary = new AccountSummaryEntity() 186 | { 187 | AccountNumber = 1253667, 188 | Balance = 55000, 189 | Currency = "INR" 190 | }; 191 | 192 | var accountTransaction = new AccountTransaction() 193 | { 194 | AccountNumber = 1253667, 195 | TransactionType = Types.TransactionType.Withdrawal, 196 | Amount = new Types.Money(65000, Types.Currency.INR) 197 | }; 198 | 199 | AccountTransactionRepositoryMock 200 | .Setup(i => i.Create(It.IsAny(), It.IsAny())) 201 | .Returns(Task.CompletedTask); 202 | 203 | AccountSummaryRepositoryMock 204 | .Setup(i => i.Read(It.IsAny())) 205 | .ReturnsAsync(accountSummary); 206 | 207 | // Act 208 | var result = TransactionServiceUnderTest.Withdraw(accountTransaction); 209 | 210 | // Assert 211 | Assert.ThrowsAsync(async () => await result); 212 | } 213 | } 214 | } 215 | } 216 | --------------------------------------------------------------------------------