Skip to content

Commit

Permalink
#397 - Write side for expense templates.
Browse files Browse the repository at this point in the history
  • Loading branch information
maraf committed Jul 6, 2021
1 parent 8cd71db commit c3aad91
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 9 deletions.
3 changes: 3 additions & 0 deletions src/Money.Api.Shared/Api/Routing/CommandMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public CommandMapper()
Add<ChangeOutcomeWhen>("outcome-change-when");
Add<DeleteOutcome>("outcome-delete");

Add<CreateExpenseTemplate>("expense-template-create");
Add<DeleteExpenseTemplate>("expense-template-delete");

Add<CreateIncome>("income-create");
Add<ChangeIncomeAmount>("income-change-amount");
Add<ChangeIncomeDescription>("income-change-description");
Expand Down
29 changes: 21 additions & 8 deletions src/Money.Api/Domain/Bootstrap/BootstrapTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,45 +164,58 @@ private void Domain(IServiceProvider provider)

queryDispatcher = new DefaultQueryDispatcher();

var snapshotProvider = new NoSnapshotProvider();
var snapshotStore = new EmptySnapshotStore();

var outcomeRepository = new AggregateRootRepository<Outcome>(
eventStore,
eventFormatter,
new ReflectionAggregateRootFactory<Outcome>(),
eventDispatcher,
new NoSnapshotProvider(),
new EmptySnapshotStore()
snapshotProvider,
snapshotStore
);

var expenseTemplateRepository = new AggregateRootRepository<ExpenseTemplate>(
eventStore,
eventFormatter,
new ReflectionAggregateRootFactory<ExpenseTemplate>(),
eventDispatcher,
snapshotProvider,
snapshotStore
);

var incomeRepository = new AggregateRootRepository<Income>(
eventStore,
eventFormatter,
new ReflectionAggregateRootFactory<Income>(),
eventDispatcher,
new NoSnapshotProvider(),
new EmptySnapshotStore()
snapshotProvider,
snapshotStore
);

var categoryRepository = new AggregateRootRepository<Category>(
eventStore,
eventFormatter,
new ReflectionAggregateRootFactory<Category>(),
eventDispatcher,
new NoSnapshotProvider(),
new EmptySnapshotStore()
snapshotProvider,
snapshotStore
);

var currencyListRepository = new AggregateRootRepository<CurrencyList>(
eventStore,
eventFormatter,
new ReflectionAggregateRootFactory<CurrencyList>(),
eventDispatcher,
new NoSnapshotProvider(),
new EmptySnapshotStore()
snapshotProvider,
snapshotStore
);

Money.BootstrapTask bootstrapTask = new Money.BootstrapTask(
commandDispatcher.Handlers,
Factory.Instance(outcomeRepository),
Factory.Instance(expenseTemplateRepository),
Factory.Instance(incomeRepository),
Factory.Instance(categoryRepository),
Factory.Instance(currencyListRepository)
Expand Down
4 changes: 4 additions & 0 deletions src/Money.Api/Domain/Hubs/ApiHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class ApiHub : Hub,
IEventHandler<CurrencyCreated>, IEventHandler<CurrencyDeleted>, IEventHandler<CurrencyDefaultChanged>, IEventHandler<CurrencySymbolChanged>,
IEventHandler<CurrencyExchangeRateSet>, IEventHandler<CurrencyExchangeRateRemoved>,
IEventHandler<OutcomeCreated>, IEventHandler<OutcomeDeleted>, IEventHandler<OutcomeAmountChanged>, IEventHandler<OutcomeDescriptionChanged>, IEventHandler<OutcomeWhenChanged>,
IEventHandler<ExpenseTemplateCreated>, IEventHandler<ExpenseTemplateDeleted>,
IEventHandler<IncomeCreated>, IEventHandler<IncomeAmountChanged>, IEventHandler<IncomeDescriptionChanged>, IEventHandler<IncomeWhenChanged>, IEventHandler<IncomeDeleted>,
IEventHandler<PasswordChanged>, IEventHandler<EmailChanged>, IEventHandler<UserPropertyChanged>
{
Expand Down Expand Up @@ -132,6 +133,9 @@ private Task RaiseEvent<T>(T payload)
Task IEventHandler<OutcomeDescriptionChanged>.HandleAsync(OutcomeDescriptionChanged payload) => RaiseEvent(payload);
Task IEventHandler<OutcomeWhenChanged>.HandleAsync(OutcomeWhenChanged payload) => RaiseEvent(payload);

Task IEventHandler<ExpenseTemplateCreated>.HandleAsync(ExpenseTemplateCreated payload) => RaiseEvent(payload);
Task IEventHandler<ExpenseTemplateDeleted>.HandleAsync(ExpenseTemplateDeleted payload) => RaiseEvent(payload);

Task IEventHandler<IncomeCreated>.HandleAsync(IncomeCreated payload) => RaiseEvent(payload);
Task IEventHandler<IncomeAmountChanged>.HandleAsync(IncomeAmountChanged payload) => RaiseEvent(payload);
Task IEventHandler<IncomeDescriptionChanged>.HandleAsync(IncomeDescriptionChanged payload) => RaiseEvent(payload);
Expand Down
85 changes: 85 additions & 0 deletions src/Money.Blazor.Host/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Blazored.LocalStorage;
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
Expand All @@ -10,8 +12,11 @@
using Money.Models;
using Money.Services;
using Neptuo.Events;
using Neptuo.Events.Handlers;
using Neptuo.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -86,6 +91,9 @@ private static void ConfigureServices(IServiceCollection services)
.AddBlazoredLocalStorage()
.AddBlazoredSessionStorage();

services
.AddTransient<IComponentActivator, ComponentActivator>();

services
.AddSingleton<TemplateService>();

Expand All @@ -111,4 +119,81 @@ private static void StartupServices(IServiceProvider services)
services.GetRequiredService<VisibilityState>();
}
}

class ComponentActivator : IComponentActivator
{
public IComponent CreateInstance(Type componentType)
{
Console.WriteLine($"CA: '{componentType.FullName}'.");

if (componentType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEventHandler<>)))
{
Console.WriteLine($"CA: '{componentType.FullName}' as 'EventAttachedComponent'.");
return new EventAttachedComponent() { Type = componentType };
}
else if(componentType.IsGenericType && componentType.GetGenericTypeDefinition() == typeof(Wrapped<>))
{
componentType = componentType.GetGenericArguments()[0];
}

return (IComponent)Activator.CreateInstance(componentType);
}
}

class Wrapped<T> : IComponent
{
public void Attach(RenderHandle renderHandle)
{
throw new NotImplementedException();
}

public Task SetParametersAsync(ParameterView parameters)
{
throw new NotImplementedException();
}
}

class EventAttachedComponent : IComponent
{
private RenderHandle _renderHandle;
private RenderFragment _cachedRenderFragment;

public EventAttachedComponent()
{
_cachedRenderFragment = Render;
}

public Type Type { get; set; }
public IDictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>();

public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}

public Task SetParametersAsync(ParameterView parameters)
{
Parameters.Clear();
foreach (var entry in parameters)
Parameters[entry.Name] = entry.Value;

_renderHandle.Render(_cachedRenderFragment);
return Task.CompletedTask;
}

void Render(RenderTreeBuilder builder)
{
builder.OpenComponent(0, typeof(Wrapped<>).MakeGenericType(Type));

if (Parameters != null)
{
foreach (var entry in Parameters)
{
builder.AddAttribute(1, entry.Key, entry.Value);
}
}

builder.CloseComponent();
}
}
}
7 changes: 7 additions & 0 deletions src/Money/BootstrapTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,27 @@ public class BootstrapTask : IBootstrapTask
{
private readonly ICommandHandlerCollection commandHandlers;
private readonly IFactory<IRepository<Outcome, IKey>> outcomeRepository;
private readonly IFactory<IRepository<ExpenseTemplate, IKey>> expenseTemplateRepository;
private readonly IFactory<IRepository<Income, IKey>> incomeRepository;
private readonly IFactory<IRepository<Category, IKey>> categoryRepository;
private readonly IFactory<IRepository<CurrencyList, IKey>> currencyListRepository;

public BootstrapTask(ICommandHandlerCollection commandHandlers,
IFactory<IRepository<Outcome, IKey>> outcomeRepository,
IFactory<IRepository<ExpenseTemplate, IKey>> expenseTemplateRepository,
IFactory<IRepository<Income, IKey>> incomeRepository,
IFactory<IRepository<Category, IKey>> categoryRepository,
IFactory<IRepository<CurrencyList, IKey>> currencyListRepository)
{
Ensure.NotNull(commandHandlers, "commandHandlers");
Ensure.NotNull(outcomeRepository, "outcomeRepository");
Ensure.NotNull(expenseTemplateRepository, "expenseTemplateRepository");
Ensure.NotNull(incomeRepository, "incomeRepository");
Ensure.NotNull(categoryRepository, "categoryRepository");
Ensure.NotNull(currencyListRepository, "currencyListRepository");
this.commandHandlers = commandHandlers;
this.outcomeRepository = outcomeRepository;
this.expenseTemplateRepository = expenseTemplateRepository;
this.incomeRepository = incomeRepository;
this.categoryRepository = categoryRepository;
this.currencyListRepository = currencyListRepository;
Expand All @@ -44,6 +48,9 @@ public void Initialize()
OutcomeHandler outcomeHandler = new OutcomeHandler(outcomeRepository);
commandHandlers.AddAll(outcomeHandler);

ExpenseTemplateHandler expenseTemplateHandler = new ExpenseTemplateHandler(expenseTemplateRepository);
commandHandlers.AddAll(expenseTemplateHandler);

IncomeHandler incomeHandler = new IncomeHandler(incomeRepository);
commandHandlers.AddAll(incomeHandler);

Expand Down
47 changes: 47 additions & 0 deletions src/Money/Commands/CreateExpenseTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Neptuo;
using Neptuo.Commands;
using Neptuo.Models.Keys;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Money.Commands
{
/// <summary>
/// Creates a new expense template.
/// </summary>
public class CreateExpenseTemplate : Command
{
/// <summary>
/// Gets an amount of the expense template.
/// </summary>
public Price Amount { get; private set; }

/// <summary>
/// Gets a description of the expense template.
/// </summary>
public string Description { get; private set; }

/// <summary>
/// Gets a category of the expense template.
/// </summary>
public IKey CategoryKey { get; private set; }

/// <summary>
/// Creates a new command for adding an expense template.
/// </summary>
/// <param name="amount">An amount of the expense template.</param>
/// <param name="description">A description of the expense template.</param>
/// <param name="when">A category of the expense template.</param>
public CreateExpenseTemplate(Price amount, string description, IKey categoryKey)
{
Ensure.NotNull(categoryKey, "categoryKey");
Amount = amount;
Description = description;
CategoryKey = categoryKey;
}
}
}
33 changes: 33 additions & 0 deletions src/Money/Commands/DeleteExpenseTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Neptuo;
using Neptuo.Commands;
using Neptuo.Models.Keys;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Money.Commands
{
/// <summary>
/// Deletes an expense template.
/// </summary>
public class DeleteExpenseTemplate : Command
{
/// <summary>
/// Gets a key of the expense template to delete.
/// </summary>
public IKey ExpenseTemplateKey { get; private set; }

/// <summary>
/// Deletes an outcome with <paramref name="expenseTemplateKey"/>.
/// </summary>
/// <param name="expenseTemplateKey">A key of the expense template to delete.</param>
public DeleteExpenseTemplate(IKey expenseTemplateKey)
{
Ensure.Condition.NotEmptyKey(expenseTemplateKey);
ExpenseTemplateKey = expenseTemplateKey;
}
}
}
26 changes: 26 additions & 0 deletions src/Money/Commands/Handlers/ExpenseTemplateHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Neptuo;
using Neptuo.Activators;
using Neptuo.Commands.Handlers;
using Neptuo.Models.Keys;
using Neptuo.Models.Repositories;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Money.Commands.Handlers
{
public class ExpenseTemplateHandler : AggregateRootCommandHandler<ExpenseTemplate>,
ICommandHandler<Envelope<CreateExpenseTemplate>>,
ICommandHandler<Envelope<DeleteExpenseTemplate>>
{
public ExpenseTemplateHandler(IFactory<IRepository<ExpenseTemplate, IKey>> repositoryFactory)
: base(repositoryFactory)
{ }

public Task HandleAsync(Envelope<CreateExpenseTemplate> envelope) => WithCommand(envelope.Body.Key).Execute(envelope, () => new ExpenseTemplate(envelope.Body.Amount, envelope.Body.Description, envelope.Body.CategoryKey));
public Task HandleAsync(Envelope<DeleteExpenseTemplate> envelope) => WithCommand(envelope.Body.Key).Execute(envelope.Body.ExpenseTemplateKey, envelope, model => model.Delete());
}
}
40 changes: 40 additions & 0 deletions src/Money/Events/ExpenseTemplateCreated.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Neptuo;
using Neptuo.Models.Keys;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Money.Events
{
/// <summary>
/// An event raised when new a expense template is created.
/// </summary>
public class ExpenseTemplateCreated : UserEvent
{
/// <summary>
/// Gets a amount of the expense template.
/// </summary>
public Price Amount { get; private set; }

/// <summary>
/// Gets a description of the expense template.
/// </summary>
public string Description { get; private set; }

/// <summary>
/// Gets a category of the expense template.
/// </summary>
public IKey CategoryKey { get; set; }

internal ExpenseTemplateCreated(Price amount, string description, IKey categoryKey)
{
Ensure.NotNull(categoryKey, "categoryKey");
Amount = amount;
Description = description;
CategoryKey = categoryKey;
}
}
}
Loading

0 comments on commit c3aad91

Please sign in to comment.