Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Accounting/AccountingDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public AccountingDbContext(DbContextOptions<AccountingDbContext> options, IPubli
_logger = logger;
}

public DbSet<Account> Accounts { get; set; }
public DbSet<Domain.Task> Tasks { get; set; }
public DbSet<Popug> Pogugs { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
Expand Down
1 change: 1 addition & 0 deletions src/Accounting/AccountingDbContextSeeder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Accounting.Domain;
using Microsoft.Extensions.Options;
using Task = System.Threading.Tasks.Task;

namespace Accounting;

Expand Down
41 changes: 41 additions & 0 deletions src/Accounting/Domain/Account.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Accounting.Domain.Events;

namespace Accounting.Domain;

public class Account : Entity
{
private readonly List<Transaction> _transactions = new();

public Account(Popug owner)
{
Owner = owner;
}

public Popug Owner { get; }
public double Balance { get; private set; }
public IReadOnlyCollection<Transaction> Transactions => _transactions.AsReadOnly();

public void Deposit(double amount, string description)
{
if (amount <= 0)
throw new ArgumentException(nameof(amount));

var deposit = new Transaction(description, 0, amount);
_transactions.Add(deposit);

Balance += amount;
_domainEvents.Add(new TransactionCommittedDomainEvent(this, deposit));
}

public void Withdraw(double amount, string description)
{
if (amount <= 0)
throw new ArgumentException(nameof(amount));

var withdrawal = new Transaction(description, amount, 0);
_transactions.Add(withdrawal);

Balance -= amount;
_domainEvents.Add(new TransactionCommittedDomainEvent(this, withdrawal));
}
}
13 changes: 13 additions & 0 deletions src/Accounting/Domain/Events/TransactionCommittedDomainEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Accounting.Domain.Events;

internal class TransactionCommittedDomainEvent : DomainEvent
{
public TransactionCommittedDomainEvent(Account account, Transaction transaction)
{
Account = account;
Transaction = transaction;
}

public Account Account { get; }
public Transaction Transaction { get; }
}
9 changes: 9 additions & 0 deletions src/Accounting/Domain/Task.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Accounting.Domain;

public class Task : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public double Fee { get; set; }
public double Reward { get; set; }
}
15 changes: 15 additions & 0 deletions src/Accounting/Domain/Transaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Accounting.Domain;

public class Transaction : Entity
{
public Transaction(string description, double credit, double debit)
{
Description = description;
Credit = credit;
Debit = debit;
}

public string Description { get; }
public double Credit { get; }
public double Debit { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using MediatR;

namespace Accounting.Integration.EventConsuming;

internal abstract class MediatrBasedMessageBrokerEventConsumer<TMessageBrokerEvent> : INotificationHandler<TMessageBrokerEvent> where TMessageBrokerEvent : MessageBrokerEvent
{
protected abstract Task Consume(TMessageBrokerEvent @event, CancellationToken cancellationToken);

public async Task Handle(TMessageBrokerEvent notification, CancellationToken cancellationToken)
{
await Consume(notification, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Accounting.Domain;

namespace Accounting.Integration.EventConsuming;

public class PopugCreatedStreamingEvent : MessageBrokerEvent
{
private static readonly MessageBrokerEventName EventName = new("Users", "Created");
private static readonly MessageBrokerEventType EventType = MessageBrokerEventType.Streaming;

public PopugCreatedStreamingEvent(Guid id, DateTime createdAt, DataType data) : base(id, EventName, EventType, 1, createdAt, data)
{
}

// public PopugCreatedStreamingEvent(DataType data, Guid id, MessageBrokerEventName name, int version, DateTime createdAt) : base(data, id, name, version, createdAt)
// {
// }

public class DataType
{
public Guid Id { get; set; }
public string Username { get; set; }
public string FullName { get; set; }
public RoleType Role { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Accounting.Domain;
using Task = System.Threading.Tasks.Task;

namespace Accounting.Integration.EventConsuming;

internal class PopugCreatedStreamingEventConsumer : MediatrBasedMessageBrokerEventConsumer<PopugCreatedStreamingEvent>
{
private readonly AccountingDbContext _dbContext;

public PopugCreatedStreamingEventConsumer(AccountingDbContext dbContext)
{
_dbContext = dbContext;
}

protected override async Task Consume(PopugCreatedStreamingEvent @event, CancellationToken cancellationToken)
{
if (@event.Data is not PopugCreatedStreamingEvent.DataType eventData)
throw new InvalidCastException();

_dbContext.Pogugs.Add(new Popug(eventData.Username, eventData.FullName, eventData.Role));
await _dbContext.SaveChangesAsync(cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TaskTracker.Integration.Events;
namespace Accounting.Integration.EventConsuming;

public class TaskCompletedBehaviourEvent : MessageBrokerEvent
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Accounting.Domain;
using Microsoft.EntityFrameworkCore;
using Task = System.Threading.Tasks.Task;

namespace Accounting.Integration.EventConsuming;

internal class TaskCompletedBehaviourEventConsumer : MediatrBasedMessageBrokerEventConsumer<TaskCompletedBehaviourEvent>
{
private readonly AccountingDbContext _dbContext;

public TaskCompletedBehaviourEventConsumer(AccountingDbContext dbContext)
{
_dbContext = dbContext;
}

protected override async Task Consume(TaskCompletedBehaviourEvent @event, CancellationToken cancellationToken)
{
if (@event.Data is not TaskCompletedBehaviourEvent.DataType eventData)
throw new InvalidCastException();

// Find account for popug

var account = await _dbContext.Accounts.FirstOrDefaultAsync(a => a.Owner.Id == eventData.AssigneeId, cancellationToken);

if (account is null)
throw new InvalidOperationException("Account not found");

// Get task price

var task = await _dbContext.Tasks.FirstOrDefaultAsync(t => t.Id == eventData.Id, cancellationToken);

if (task is null)
throw new InvalidOperationException("Task not found");

// Give popug reward

account.Deposit(task.Reward, $"Reward for {eventData.Id}");
await _dbContext.SaveChangesAsync(cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TaskTracker.Integration.Events;
namespace Accounting.Integration.EventConsuming;

public class TaskCreatedBehaviourEvent : MessageBrokerEvent
{
Expand All @@ -12,11 +12,6 @@ public TaskCreatedBehaviourEvent(Guid id, DateTime createdAt, DataType data) : b
public class DataType
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public double Fee { get; set; }
public double Reward { get; set; }
public string Status { get; set; }
public Guid AssigneeId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Accounting.Domain;
using Microsoft.EntityFrameworkCore;
using Task = System.Threading.Tasks.Task;

namespace Accounting.Integration.EventConsuming;

internal class TaskCreatedBehaviourEventConsumer : MediatrBasedMessageBrokerEventConsumer<TaskCreatedBehaviourEvent>
{
private readonly AccountingDbContext _dbContext;

public TaskCreatedBehaviourEventConsumer(AccountingDbContext dbContext)
{
_dbContext = dbContext;
}

// TODO: Introduce Commands layer because withdrawal is duplicated in TaskReassignedHandler
protected override async Task Consume(TaskCreatedBehaviourEvent @event, CancellationToken cancellationToken)
{
if (@event.Data is not TaskCreatedBehaviourEvent.DataType eventData)
throw new InvalidCastException();

// Find account for popug

var account = await _dbContext.Accounts.FirstOrDefaultAsync(a => a.Owner.Id == eventData.AssigneeId, cancellationToken);

if (account is null)
throw new InvalidOperationException("Account not found");

// Get task price

var task = await _dbContext.Tasks.FirstOrDefaultAsync(t => t.Id == eventData.Id, cancellationToken);

if (task is null)
throw new InvalidOperationException("Task not found");

// Take fee from popug

account.Withdraw(task.Fee, $"Fee for {eventData.Id} assigned");
await _dbContext.SaveChangesAsync(cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Accounting.Integration.EventConsuming;

public class TaskCreatedStreamingEvent : MessageBrokerEvent
{
private static readonly MessageBrokerEventName EventName = new("Tasks", "Created");
private static readonly MessageBrokerEventType EventType = MessageBrokerEventType.Streaming;

public TaskCreatedStreamingEvent(Guid id, DateTime createdAt, DataType data) : base(id, EventName, EventType, 2, createdAt, data)
{
}

public class DataType
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public double Fee { get; set; }
public double Reward { get; set; }
public TaskStatus Status { get; set; }
public Guid AssigneeId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Accounting.Integration.EventConsuming;

public class TaskCreatedStreamingEventV2 : MessageBrokerEvent
{
private static readonly MessageBrokerEventName EventName = new("Tasks", "Created");
private static readonly MessageBrokerEventType EventType = MessageBrokerEventType.Streaming;

public TaskCreatedStreamingEventV2(Guid id, DateTime createdAt, DataType data) : base(id, EventName, EventType, 2, createdAt, data)
{
}

public class DataType
{
public Guid Id { get; set; }
public string JiraId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public double Fee { get; set; }
public double Reward { get; set; }
public string Status { get; set; }
public Guid AssigneeId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TaskTracker.Integration.Events;
namespace Accounting.Integration.EventConsuming;

public class TaskReassignedBehaviourEvent : MessageBrokerEvent
{
Expand All @@ -13,6 +13,5 @@ public class DataType
{
public Guid Id { get; set; }
public Guid NewAssigneeId { get; set; }
public Guid PreviousAssigneeId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Accounting.Domain;
using Microsoft.EntityFrameworkCore;
using Task = System.Threading.Tasks.Task;

namespace Accounting.Integration.EventConsuming;

internal class TaskReassignedBehaviourEventConsumer : MediatrBasedMessageBrokerEventConsumer<TaskReassignedBehaviourEvent>
{
private readonly AccountingDbContext _dbContext;

public TaskReassignedBehaviourEventConsumer(AccountingDbContext dbContext)
{
_dbContext = dbContext;
}

protected override async Task Consume(TaskReassignedBehaviourEvent @event, CancellationToken cancellationToken)
{
if (@event.Data is not TaskReassignedBehaviourEvent.DataType eventData)
throw new InvalidCastException();

// Find account for popug

var account = await _dbContext.Accounts.FirstOrDefaultAsync(a => a.Owner.Id == eventData.NewAssigneeId, cancellationToken);

if (account is null)
throw new InvalidOperationException("Account not found");

// Get task price

var task = await _dbContext.Tasks.FirstOrDefaultAsync(t => t.Id == eventData.Id, cancellationToken);

if (task is null)
throw new InvalidOperationException("Task not found");

// Take fee from popug

account.Withdraw(task.Fee, $"Fee for {eventData.Id} assigned");
await _dbContext.SaveChangesAsync(cancellationToken);
}
}
Loading