diff --git a/Application/Budgets/Commands/CreateBudgetCommand.cs b/Application/Budgets/Commands/CreateBudgetCommand.cs index f92da03..e17cee5 100644 --- a/Application/Budgets/Commands/CreateBudgetCommand.cs +++ b/Application/Budgets/Commands/CreateBudgetCommand.cs @@ -3,4 +3,4 @@ namespace Application.Budgets; -public sealed record CreateBudgetCommand(Guid Id, Decimal MaximumAmount, Currency Currency) : ICommand; \ No newline at end of file +public sealed record CreateBudgetCommand(Guid Id, Decimal MaximumAmount, Currency Currency, Guid Owner) : ICommand; \ No newline at end of file diff --git a/Application/Budgets/Commands/Handlers/CreateBudgetCommandHandler.cs b/Application/Budgets/Commands/Handlers/CreateBudgetCommandHandler.cs index 97ffc82..86c19c2 100644 --- a/Application/Budgets/Commands/Handlers/CreateBudgetCommandHandler.cs +++ b/Application/Budgets/Commands/Handlers/CreateBudgetCommandHandler.cs @@ -1,5 +1,6 @@ using Domain.Budgets; using Application.Shared.Command; +using Domain; namespace Application.Budgets; @@ -17,8 +18,9 @@ public async Task Handle(CreateBudgetCommand request, CancellationToken cancella var id = BudgetId.Create(request.Id); var amount = BudgetAmount.Create(request.MaximumAmount, request.Currency); var period = BudgetPeriod.CreateMonthly(); + var owner = UserId.Create(request.Owner); - var budget = Budget.Create(id, amount, period); + var budget = Budget.Create(id, amount, period, owner); await _budgetRepository.Add(budget); } diff --git a/Application/Expenses/Commands/CreateExpenseCommand.cs b/Application/Expenses/Commands/CreateExpenseCommand.cs index f9dd6d6..07f97ff 100644 --- a/Application/Expenses/Commands/CreateExpenseCommand.cs +++ b/Application/Expenses/Commands/CreateExpenseCommand.cs @@ -3,4 +3,4 @@ namespace Application.Expenses.Commands; -public sealed record CreateExpenseCommand(Guid Id, decimal Amount, Currency Currency, string Description) : ICommand; +public sealed record CreateExpenseCommand(Guid Id, decimal Amount, Currency Currency, string Description, Guid User) : ICommand; diff --git a/Application/Expenses/Commands/Handlers/CreateExpenseCommandHandler.cs b/Application/Expenses/Commands/Handlers/CreateExpenseCommandHandler.cs index 3ed58bc..daf293a 100644 --- a/Application/Expenses/Commands/Handlers/CreateExpenseCommandHandler.cs +++ b/Application/Expenses/Commands/Handlers/CreateExpenseCommandHandler.cs @@ -1,4 +1,5 @@ using Application.Shared.Command; +using Domain; using Domain.Expenses; namespace Application.Expenses.Commands; @@ -17,10 +18,11 @@ public async Task Handle(CreateExpenseCommand request, CancellationToken cancell var id = ExpenseId.Create(request.Id); var amount = ExpenseAmount.Create(request.Amount, request.Currency); var description = ExpenseDescription.Create(request.Description); + var user = UserId.Create(request.User); - var expense = Expense.Create(id: id, amount: amount, description: description); + var expense = Expense.Create(id: id, amount: amount, description: description, user: user); - await this._expenseRepository.Add(expense); + await _expenseRepository.Add(expense); } } diff --git a/Application/Groups/CreateGroupCommand.cs b/Application/Groups/CreateGroupCommand.cs new file mode 100644 index 0000000..7adc566 --- /dev/null +++ b/Application/Groups/CreateGroupCommand.cs @@ -0,0 +1,5 @@ +using Application.Shared.Command; + +namespace Application; + +public sealed record CreateGroupCommand(Guid GroupId, string GroupName, Guid UserId) : ICommand; diff --git a/Application/Groups/Handlers/CreateGroupCommandHandler.cs b/Application/Groups/Handlers/CreateGroupCommandHandler.cs new file mode 100644 index 0000000..2ee103d --- /dev/null +++ b/Application/Groups/Handlers/CreateGroupCommandHandler.cs @@ -0,0 +1,26 @@ +using Application.Shared.Command; +using Domain; +using Domain.Groups; + +namespace Application; + +public class CreateGroupCommandHandler : ICommandHandler +{ + private readonly IGroupRepository _groupRepository; + + public CreateGroupCommandHandler(IGroupRepository groupRepository) + { + _groupRepository = groupRepository; + } + + public async Task Handle(CreateGroupCommand request, CancellationToken cancellationToken = default) + { + var name = GroupName.Create(request.GroupName); + var id = GroupId.Create(request.GroupId); + var admin = UserId.Create(request.UserId); + + var group = Group.Create(id, name, admin); + + await _groupRepository.Add(group); + } +} diff --git a/Domain/Budgets/Entities/Budget.cs b/Domain/Budgets/Entities/Budget.cs index f31e1de..eb5ed65 100644 --- a/Domain/Budgets/Entities/Budget.cs +++ b/Domain/Budgets/Entities/Budget.cs @@ -7,6 +7,8 @@ public class Budget : AggregateRoot #region Properties public BudgetId Id { get; private set; } + public UserId Owner { get; private set; } + public BudgetAmount MaximumAmount { get; private set; } private decimal RemainingAmountAfter(Decimal amount) => MaximumAmount.Amount - Records.Sum(r => r.Amount) - amount; @@ -27,18 +29,19 @@ private Budget() { } - protected Budget(BudgetId id, BudgetAmount maximumAmount, BudgetPeriod period) + protected Budget(BudgetId id, BudgetAmount maximumAmount, BudgetPeriod period, UserId owner) { Id = id; MaximumAmount = maximumAmount; Period = period; + Owner = owner; } - public static Budget Create(BudgetId id, BudgetAmount maximumAmount, BudgetPeriod period) + public static Budget Create(BudgetId id, BudgetAmount maximumAmount, BudgetPeriod period, UserId owner) { - var budget = new Budget(id, maximumAmount, period); + var budget = new Budget(id, maximumAmount, period, owner); - budget.RecordEvent(new BudgetCreatedEvent(id.Value)); + budget.RecordEvent(new BudgetCreatedEvent(id.Value, owner.Value)); return budget; } diff --git a/Domain/Budgets/Events/BudgetCreatedEvent.cs b/Domain/Budgets/Events/BudgetCreatedEvent.cs index 5d70a64..d1da94e 100644 --- a/Domain/Budgets/Events/BudgetCreatedEvent.cs +++ b/Domain/Budgets/Events/BudgetCreatedEvent.cs @@ -2,4 +2,4 @@ namespace Domain.Budgets; -public sealed record BudgetCreatedEvent(Guid Id) : IDomainEvent; \ No newline at end of file +public sealed record BudgetCreatedEvent(Guid Id, Guid OwnerId) : IDomainEvent; \ No newline at end of file diff --git a/Domain/Expenses/Entities/Expense.cs b/Domain/Expenses/Entities/Expense.cs index b9e23e5..c795f7c 100644 --- a/Domain/Expenses/Entities/Expense.cs +++ b/Domain/Expenses/Entities/Expense.cs @@ -6,6 +6,8 @@ public class Expense : AggregateRoot { public ExpenseId Id { get; private set; } + public UserId User { get; private set; } + public ExpenseAmount Amount { get; private set; } public ExpenseDescription Description { get; private set; } @@ -14,18 +16,19 @@ private Expense() { } - protected Expense(ExpenseId id, ExpenseAmount amount, ExpenseDescription description) + protected Expense(ExpenseId id, ExpenseAmount amount, ExpenseDescription description, UserId user) { Id = id; Amount = amount; Description = description; + User = user; } - public static Expense Create(ExpenseId id, ExpenseAmount amount, ExpenseDescription description) + public static Expense Create(ExpenseId id, ExpenseAmount amount, ExpenseDescription description, UserId user) { - var expense = new Expense(id: id, amount: amount, description: description); + var expense = new Expense(id: id, amount: amount, description: description, user: user); - expense.RecordEvent(new ExpenseCreatedEvent(id.Value, amount.Quantity, DateTime.Now)); + expense.RecordEvent(new ExpenseCreatedEvent(id.Value, amount.Quantity, DateTime.Now, user.Value)); return expense; } diff --git a/Domain/Expenses/Events/ExpenseCreatedEvent.cs b/Domain/Expenses/Events/ExpenseCreatedEvent.cs index 7561caa..d5e6ee2 100644 --- a/Domain/Expenses/Events/ExpenseCreatedEvent.cs +++ b/Domain/Expenses/Events/ExpenseCreatedEvent.cs @@ -2,7 +2,7 @@ namespace Domain.Expenses; -public sealed record ExpenseCreatedEvent : IDomainEvent +public sealed record ExpenseCreatedEvent : IDomainEvent { public Guid Id { get; set; } @@ -10,16 +10,17 @@ public sealed record ExpenseCreatedEvent : IDomainEvent public DateTime CreatedAt { get; set; } - + public Guid User { get; set; } + public ExpenseCreatedEvent() { } - - public ExpenseCreatedEvent(Guid id, Decimal amount, DateTime createdAt) + + public ExpenseCreatedEvent(Guid id, Decimal amount, DateTime createdAt, Guid user) { Id = id; Amount = amount; CreatedAt = createdAt; - } - + User = user; + } } diff --git a/Domain/Groups/Entities/Group.cs b/Domain/Groups/Entities/Group.cs new file mode 100644 index 0000000..159e1ab --- /dev/null +++ b/Domain/Groups/Entities/Group.cs @@ -0,0 +1,42 @@ +using Domain.Shared.Base; + +namespace Domain.Groups; + +public class Group : AggregateRoot +{ + public GroupId Id { get; private set; } + + public GroupName Name { get; private set; } + + public UserId Admin { get; private set; } + + public HashSet Members { get; private set; } = new(); + + public List Records { get; private set; } = new(); + + private Group() + { + } + + protected Group(GroupId id, GroupName name, UserId admin) + { + } + + public static Group Create(GroupId id, GroupName name, UserId admin) + { + var group = new Group(id, name, admin); + + group.RecordEvent(new GroupCreatedEvent(id.Value, name.Value, admin.Value)); + + return group; + } + + public void Join(UserId user) + { + if (Members.Contains(user) is true) + throw new AlreadyMemberException(user, this); + + Members.Add(user); + RecordEvent(new MemberJoinedEvent(user.Value, this.Id.Value)); + } +} diff --git a/Domain/Groups/Errors/AlreadyMemberException.cs b/Domain/Groups/Errors/AlreadyMemberException.cs new file mode 100644 index 0000000..9f40f26 --- /dev/null +++ b/Domain/Groups/Errors/AlreadyMemberException.cs @@ -0,0 +1,10 @@ +using Domain.Shared.Base; + +namespace Domain.Groups; + +public class AlreadyMemberException : DomainException +{ + public AlreadyMemberException(UserId user, Group group) : base($"The user with ID [{user.Value}] is alread member of group [{group.Name.Value} | {group.Id.Value}]") + { + } +} diff --git a/Domain/Groups/Events/GroupCreatedEvent.cs b/Domain/Groups/Events/GroupCreatedEvent.cs new file mode 100644 index 0000000..f9ea0fd --- /dev/null +++ b/Domain/Groups/Events/GroupCreatedEvent.cs @@ -0,0 +1,5 @@ +using Domain.Shared.Base; + +namespace Domain.Groups; + +public sealed class GroupCreatedEvent(Guid GroupId, string GroupName, Guid AdminId) : IDomainEvent; diff --git a/Domain/Groups/Events/MemberJoinedEvent.cs b/Domain/Groups/Events/MemberJoinedEvent.cs new file mode 100644 index 0000000..8d23c2f --- /dev/null +++ b/Domain/Groups/Events/MemberJoinedEvent.cs @@ -0,0 +1,5 @@ +using Domain.Shared.Base; + +namespace Domain.Groups; + +public sealed class MemberJoinedEvent(Guid MemberId, Guid GroupId) : IDomainEvent; diff --git a/Domain/Groups/Repository/IGroupRepository.cs b/Domain/Groups/Repository/IGroupRepository.cs new file mode 100644 index 0000000..5382017 --- /dev/null +++ b/Domain/Groups/Repository/IGroupRepository.cs @@ -0,0 +1,8 @@ +using Domain.Shared.Base; + +namespace Domain.Groups; + +public interface IGroupRepository : IRepository +{ + public Task Delete(Group group); +} diff --git a/Domain/Groups/ValueObjects/GroupId.cs b/Domain/Groups/ValueObjects/GroupId.cs new file mode 100644 index 0000000..4c33213 --- /dev/null +++ b/Domain/Groups/ValueObjects/GroupId.cs @@ -0,0 +1,6 @@ +namespace Domain.Groups; + +public record GroupId(Guid Value) +{ + public static GroupId Create(Guid value) => new(value); +} diff --git a/Domain/Groups/ValueObjects/GroupName.cs b/Domain/Groups/ValueObjects/GroupName.cs new file mode 100644 index 0000000..d732737 --- /dev/null +++ b/Domain/Groups/ValueObjects/GroupName.cs @@ -0,0 +1,6 @@ +namespace Domain.Groups; + +public record GroupName(string Value) +{ + public static GroupName Create(string name) => new(name); +} diff --git a/Domain/Groups/ValueObjects/GroupRecords.cs b/Domain/Groups/ValueObjects/GroupRecords.cs new file mode 100644 index 0000000..f18546e --- /dev/null +++ b/Domain/Groups/ValueObjects/GroupRecords.cs @@ -0,0 +1,6 @@ +namespace Domain.Groups; + +public class GroupRecords +{ + +} diff --git a/Persistance/Entities/Budgets/Configuration/BudgetConfiguration.cs b/Persistance/Entities/Budgets/Configuration/BudgetConfiguration.cs index fe74ff4..3a6a7e2 100644 --- a/Persistance/Entities/Budgets/Configuration/BudgetConfiguration.cs +++ b/Persistance/Entities/Budgets/Configuration/BudgetConfiguration.cs @@ -1,4 +1,5 @@ -using Domain.Budgets; +using Domain; +using Domain.Budgets; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Newtonsoft.Json; @@ -15,6 +16,11 @@ public void Configure(EntityTypeBuilder builder) .HasConversion( src => src.Value, raw => BudgetId.Create(raw)); + + builder.Property(budget => budget.Owner) + .HasConversion( + src => src.Value, + raw => UserId.Create(raw)); builder.ComplexProperty(budget => budget.MaximumAmount); diff --git a/Persistance/Entities/Expenses/Configuration/ExpenseConfiguration.cs b/Persistance/Entities/Expenses/Configuration/ExpenseConfiguration.cs index 76dc534..f4b4210 100644 --- a/Persistance/Entities/Expenses/Configuration/ExpenseConfiguration.cs +++ b/Persistance/Entities/Expenses/Configuration/ExpenseConfiguration.cs @@ -1,4 +1,5 @@ -using Domain.Expenses; +using Domain; +using Domain.Expenses; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Newtonsoft.Json; @@ -21,6 +22,11 @@ public void Configure(EntityTypeBuilder builder) src => src.Value, raw => ExpenseId.Create(raw)); + builder.Property(expense => expense.User) + .HasConversion( + src => src.Value, + raw => UserId.Create(raw)); + builder.ComplexProperty(expense => expense.Amount); builder.Property(expense => expense.Description) diff --git a/Persistance/Migrations/20231116183412_Owner.Designer.cs b/Persistance/Migrations/20231116183412_Owner.Designer.cs new file mode 100644 index 0000000..46c1a00 --- /dev/null +++ b/Persistance/Migrations/20231116183412_Owner.Designer.cs @@ -0,0 +1,260 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Persistance; + +#nullable disable + +namespace Persistance.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20231116183412_Owner")] + partial class Owner + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("BudgetBudgetAlert", b => + { + b.Property("AlertsBudgetAlertId") + .HasColumnType("uuid"); + + b.Property("BudgetId") + .HasColumnType("uuid"); + + b.HasKey("AlertsBudgetAlertId", "BudgetId"); + + b.HasIndex("BudgetId"); + + b.ToTable("BudgetBudgetAlert"); + }); + + modelBuilder.Entity("BudgetBudgetRecord", b => + { + b.Property("BudgetId") + .HasColumnType("uuid"); + + b.Property("RecordsBudgetRecordId") + .HasColumnType("uuid"); + + b.HasKey("BudgetId", "RecordsBudgetRecordId"); + + b.HasIndex("RecordsBudgetRecordId"); + + b.ToTable("BudgetBudgetRecord"); + }); + + modelBuilder.Entity("Domain.Budgets.Budget", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.ComplexProperty>("MaximumAmount", "Domain.Budgets.Budget.MaximumAmount#BudgetAmount", b1 => + { + b1.IsRequired(); + + b1.Property("Amount") + .HasColumnType("numeric"); + + b1.Property("Currency") + .HasColumnType("integer"); + }); + + b.ComplexProperty>("Period", "Domain.Budgets.Budget.Period#BudgetPeriod", b1 => + { + b1.IsRequired(); + + b1.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b1.Property("StartDate") + .HasColumnType("timestamp with time zone"); + }); + + b.HasKey("Id"); + + b.ToTable("Budgets"); + }); + + modelBuilder.Entity("Domain.Budgets.BudgetAlert", b => + { + b.Property("BudgetAlertId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.HasKey("BudgetAlertId"); + + b.HasIndex("BudgetAlertId"); + + b.ToTable("BudgetAlert"); + + b.HasDiscriminator("Discriminator").HasValue("BudgetAlert"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Domain.Budgets.BudgetRecord", b => + { + b.Property("BudgetRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .HasColumnType("integer"); + + b.Property("ExpenseId") + .HasColumnType("uuid"); + + b.HasKey("BudgetRecordId"); + + b.HasIndex("BudgetRecordId"); + + b.ToTable("BudgetRecord"); + }); + + modelBuilder.Entity("Domain.Expenses.Expense", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Description"); + + b.Property("User") + .HasColumnType("uuid"); + + b.ComplexProperty>("Amount", "Domain.Expenses.Expense.Amount#ExpenseAmount", b1 => + { + b1.IsRequired(); + + b1.Property("Currency") + .HasColumnType("integer"); + + b1.Property("Quantity") + .HasColumnType("numeric"); + }); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("Expenses"); + }); + + modelBuilder.Entity("Domain.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Mail") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Mail"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Persistance.Outbox.OutboxRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("ProcessedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("OutboxRecords"); + }); + + modelBuilder.Entity("Domain.Budgets.DateBudgetAlert", b => + { + b.HasBaseType("Domain.Budgets.BudgetAlert"); + + b.Property("AlertingDate") + .HasColumnType("timestamp with time zone"); + + b.HasDiscriminator().HasValue("DateBudgetAlert"); + }); + + modelBuilder.Entity("BudgetBudgetAlert", b => + { + b.HasOne("Domain.Budgets.BudgetAlert", null) + .WithMany() + .HasForeignKey("AlertsBudgetAlertId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Budgets.Budget", null) + .WithMany() + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BudgetBudgetRecord", b => + { + b.HasOne("Domain.Budgets.Budget", null) + .WithMany() + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Budgets.BudgetRecord", null) + .WithMany() + .HasForeignKey("RecordsBudgetRecordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistance/Migrations/20231116183412_Owner.cs b/Persistance/Migrations/20231116183412_Owner.cs new file mode 100644 index 0000000..297aa5f --- /dev/null +++ b/Persistance/Migrations/20231116183412_Owner.cs @@ -0,0 +1,86 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistance.Migrations +{ + /// + public partial class Owner : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "User", + table: "Expenses", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "Owner", + table: "Budgets", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AlterColumn( + name: "Discriminator", + table: "BudgetAlert", + type: "character varying(21)", + maxLength: 21, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(13)", + oldMaxLength: 13); + + migrationBuilder.CreateTable( + name: "User", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Mail = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_User_Id", + table: "User", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_User_Mail", + table: "User", + column: "Mail"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "User"); + + migrationBuilder.DropColumn( + name: "User", + table: "Expenses"); + + migrationBuilder.DropColumn( + name: "Owner", + table: "Budgets"); + + migrationBuilder.AlterColumn( + name: "Discriminator", + table: "BudgetAlert", + type: "character varying(13)", + maxLength: 13, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(21)", + oldMaxLength: 21); + } + } +} diff --git a/Persistance/Migrations/ApplicationDbContextModelSnapshot.cs b/Persistance/Migrations/ApplicationDbContextModelSnapshot.cs index 3b1adae..097403f 100644 --- a/Persistance/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Persistance/Migrations/ApplicationDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -58,6 +58,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("uuid"); + b.Property("Owner") + .HasColumnType("uuid"); + b.ComplexProperty>("MaximumAmount", "Domain.Budgets.Budget.MaximumAmount#BudgetAmount", b1 => { b1.IsRequired(); @@ -93,8 +96,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Discriminator") .IsRequired() - .HasMaxLength(13) - .HasColumnType("character varying(13)"); + .HasMaxLength(21) + .HasColumnType("character varying(21)"); b.HasKey("BudgetAlertId"); @@ -142,6 +145,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("Description"); + b.Property("User") + .HasColumnType("uuid"); + b.ComplexProperty>("Amount", "Domain.Expenses.Expense.Amount#ExpenseAmount", b1 => { b1.IsRequired(); @@ -160,6 +166,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Expenses"); }); + modelBuilder.Entity("Domain.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Mail") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Mail"); + + b.ToTable("User"); + }); + modelBuilder.Entity("Persistance.Outbox.OutboxRecord", b => { b.Property("Id") @@ -188,14 +212,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OutboxRecords"); }); - modelBuilder.Entity("Domain.DateAlert", b => + modelBuilder.Entity("Domain.Budgets.DateBudgetAlert", b => { b.HasBaseType("Domain.Budgets.BudgetAlert"); b.Property("AlertingDate") .HasColumnType("timestamp with time zone"); - b.HasDiscriminator().HasValue("DateAlert"); + b.HasDiscriminator().HasValue("DateBudgetAlert"); }); modelBuilder.Entity("BudgetBudgetAlert", b => diff --git a/Unit/Budgets/Handlers/Commands/CreateBudgetCommandHandler.cs b/Unit/Budgets/Handlers/Commands/CreateBudgetCommandHandler.cs index 6dfe243..6c8a6ea 100644 --- a/Unit/Budgets/Handlers/Commands/CreateBudgetCommandHandler.cs +++ b/Unit/Budgets/Handlers/Commands/CreateBudgetCommandHandler.cs @@ -25,7 +25,8 @@ public async Task Handle_ShouldCreateBudget() var command = new CreateBudgetCommand( expectedBudget.Id.Value, expectedBudget.MaximumAmount.Amount, - expectedBudget.MaximumAmount.Currency + expectedBudget.MaximumAmount.Currency, + Guid.NewGuid() ); // When diff --git a/Unit/Budgets/Handlers/Events/ExpenseCreatedEventHandler.cs b/Unit/Budgets/Handlers/Events/ExpenseCreatedEventHandler.cs index ae08f73..e6910f4 100644 --- a/Unit/Budgets/Handlers/Events/ExpenseCreatedEventHandler.cs +++ b/Unit/Budgets/Handlers/Events/ExpenseCreatedEventHandler.cs @@ -3,10 +3,8 @@ using Domain.Budgets; using Domain.Expenses; using FluentAssertions; -using Microsoft.VisualBasic; using Moq; using Moq.AutoMock; -using Unit.Random; namespace Unit; @@ -61,7 +59,7 @@ public async void Handler_ShouldBlockExpense_WhenLastRecordExceedAmount() .Setup(repo => repo.FindAll()) .ReturnsAsync(new List() { budget }); - var evnt = new ExpenseCreatedEvent(Guid.NewGuid(), 100, DateTime.Now); + var evnt = new ExpenseCreatedEvent(Guid.NewGuid(), 100, DateTime.Now, Guid.NewGuid()); // When await handler.Handle(evnt); @@ -72,7 +70,7 @@ public async void Handler_ShouldBlockExpense_WhenLastRecordExceedAmount() } [Fact] - public async void Handle_ShouldThrowException_WhenBudgetIsBlocked() + public void Handle_ShouldThrowException_WhenBudgetIsBlocked() { // Given var budget = new BudgetMother() @@ -84,7 +82,7 @@ public async void Handle_ShouldThrowException_WhenBudgetIsBlocked() .Setup(repo => repo.FindAll()) .ReturnsAsync(new List() { budget }); - var evnt = new ExpenseCreatedEvent(Guid.NewGuid(), 1_000, DateTime.Now); + var evnt = new ExpenseCreatedEvent(Guid.NewGuid(), 1_000, DateTime.Now, Guid.NewGuid()); // When var handle = async () => await handler.Handle(evnt); diff --git a/Unit/Budgets/Mother/BudgetMother.cs b/Unit/Budgets/Mother/BudgetMother.cs index 86b2034..a159386 100644 --- a/Unit/Budgets/Mother/BudgetMother.cs +++ b/Unit/Budgets/Mother/BudgetMother.cs @@ -1,9 +1,7 @@ -using System.Diagnostics.Tracing; -using Bogus; +using Bogus; using Bogus.Extensions.Norway; using Domain.Budgets; using Domain.Shared.ValueObjects; -using Unit.Random; namespace Unit; diff --git a/Unit/Expenses/Mother/CreateExpenseCommandMother.cs b/Unit/Expenses/Mother/CreateExpenseCommandMother.cs index 7ae2e95..dd8bec1 100644 --- a/Unit/Expenses/Mother/CreateExpenseCommandMother.cs +++ b/Unit/Expenses/Mother/CreateExpenseCommandMother.cs @@ -6,5 +6,10 @@ namespace Unit; public static class CreateExpenseCommandMother { public static CreateExpenseCommand FromExpense(Expense expense) => - new CreateExpenseCommand(expense.Id.Value, expense.Amount.Quantity, expense.Amount.Currency, expense.Description.Value); + new CreateExpenseCommand( + expense.Id.Value, + expense.Amount.Quantity, + expense.Amount.Currency, + expense.Description.Value, + Guid.NewGuid()); } diff --git a/Unit/Expenses/Mother/ExpenseCreatedEventMother.cs b/Unit/Expenses/Mother/ExpenseCreatedEventMother.cs index 4ff88ad..a95f42e 100644 --- a/Unit/Expenses/Mother/ExpenseCreatedEventMother.cs +++ b/Unit/Expenses/Mother/ExpenseCreatedEventMother.cs @@ -1,4 +1,5 @@ using Bogus; +using Domain; using Domain.Expenses; namespace Unit; @@ -6,6 +7,7 @@ namespace Unit; public class ExpenseCreatedEventMother { private readonly Faker faker = new Faker() + .RuleFor(evnt => evnt.User, faker => faker.Random.Guid()) .RuleFor(evnt => evnt.Id, faker => faker.Random.Guid()) .RuleFor(evnt => evnt.Amount, faker => faker.Random.Decimal(max: 10_0)) .RuleFor(evnt => evnt.CreatedAt, faker => faker.Date.Past()); diff --git a/Unit/Expenses/Mother/ExpenseMother.cs b/Unit/Expenses/Mother/ExpenseMother.cs index d5ab4aa..b6bc915 100644 --- a/Unit/Expenses/Mother/ExpenseMother.cs +++ b/Unit/Expenses/Mother/ExpenseMother.cs @@ -1,9 +1,6 @@ -using System.Net; -using System.Reflection.Metadata.Ecma335; -using Bogus; +using Bogus; using Domain.Expenses; using Domain.Shared.ValueObjects; -using Unit.Random; namespace Unit; diff --git a/Unit/Expenses/Random/RandomCurrencyGenerator.cs b/Unit/Expenses/Random/RandomCurrencyGenerator.cs deleted file mode 100644 index 2376e41..0000000 --- a/Unit/Expenses/Random/RandomCurrencyGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Bogus; -using Domain.Shared.ValueObjects; - -namespace Unit; - -public static class RandomCurrencyGenerator -{ - public static Currency Random() => - new Faker().PickRandom(); -} diff --git a/Unit/Expenses/Random/RandomDateGenerator.cs b/Unit/Expenses/Random/RandomDateGenerator.cs deleted file mode 100644 index 1bec662..0000000 --- a/Unit/Expenses/Random/RandomDateGenerator.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Bogus; - -namespace Unit; - -public class RandomDateGenerator -{ - public static DateTime Future() => - new Faker().Date.Future(); -} diff --git a/Unit/Expenses/Random/RandomNumberGenerator.cs b/Unit/Expenses/Random/RandomNumberGenerator.cs deleted file mode 100644 index a940a9a..0000000 --- a/Unit/Expenses/Random/RandomNumberGenerator.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Bogus; - -namespace Unit.Random; - -public static class RandomNumberGenerator -{ - public static Decimal Random(decimal min = 0, decimal max = decimal.MaxValue) => - new Faker().Random.Decimal(min, max); -} diff --git a/Unit/Expenses/Random/RandomSentenceGenerator.cs b/Unit/Expenses/Random/RandomSentenceGenerator.cs deleted file mode 100644 index df4799d..0000000 --- a/Unit/Expenses/Random/RandomSentenceGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Unit.Random; - -public class RandomSentenceGenerator -{ - public static string One() => - new Bogus.DataSets.Lorem(locale: "es").Sentence(5); - - public static string Paragraph() => - new Bogus.DataSets.Lorem(locale: "es").Paragraph(5); -} \ No newline at end of file diff --git a/Unit/Groups/Handler/Command/CreateGroupCommandHandler.cs b/Unit/Groups/Handler/Command/CreateGroupCommandHandler.cs new file mode 100644 index 0000000..7da0ddf --- /dev/null +++ b/Unit/Groups/Handler/Command/CreateGroupCommandHandler.cs @@ -0,0 +1,36 @@ +using Application; +using Domain.Groups; +using Moq; +using Moq.AutoMock; + +namespace Unit; + +public class TestCreateGroupCommandHandler +{ + private readonly AutoMocker mocker = new AutoMocker(); + + private readonly CreateGroupCommandHandler handler; + + public TestCreateGroupCommandHandler() + { + handler = mocker.CreateInstance(); + } + + [Fact] + public async Task Handle_ShouldCreateGroup() + { + // Given + var group = GroupMother.Random(); + + var command = new CreateGroupCommand(group.Id.Value, group.Name.Value, Guid.NewGuid()); + + // When + await handler.Handle(command); + + // Then + mocker.GetMock() + .Verify(repo => repo.Add( + It.IsAny()), + Times.Once); + } +} diff --git a/Unit/Groups/Mother/GroupMother.cs b/Unit/Groups/Mother/GroupMother.cs new file mode 100644 index 0000000..db121a3 --- /dev/null +++ b/Unit/Groups/Mother/GroupMother.cs @@ -0,0 +1,19 @@ +using Bogus; +using Domain; +using Domain.Groups; + +namespace Unit; + +public class GroupMother +{ + private readonly Faker faker = new Faker() + .UsePrivateConstructor() + .RuleFor(x => x.Id, f => GroupId.Create(f.Random.Guid())) + .RuleFor(x => x.Name, f => GroupName.Create(f.Lorem.Word())) + .RuleFor(x => x.Admin, f => UserId.Create(f.Random.Guid())); + + public Group Build() => faker.Generate(); + + public static Group Random() => + new GroupMother().Build(); +} diff --git a/Unit/Unit.csproj b/Unit/Unit.csproj index 318f96f..3c99244 100644 --- a/Unit/Unit.csproj +++ b/Unit/Unit.csproj @@ -36,6 +36,5 @@ - \ No newline at end of file