Skip to content

Commit

Permalink
Port TodosController
Browse files Browse the repository at this point in the history
  • Loading branch information
bartelink committed Jan 4, 2019
1 parent 15a2d27 commit 41691fa
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 79 deletions.
2 changes: 1 addition & 1 deletion equinox-web-csharp/Domain/Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE;DEBUG;todos</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions equinox-web-csharp/Domain/Todo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ public static State Fold(State origin, IEnumerable<IEvent> xs)
}
return new State(nextId, items.ToArray());
}

/// Determines whether a given event represents a checkpoint that implies we don't need to see any preceding events
public static bool IsOrigin(IEvent e) => e is Events.Cleared || e is Events.Compacted;

/// Prepares an Event that encodes all relevant aspects of a State such that `evolve` can rehydrate a complete State from it
public static IEvent Compact(State state) => new Events.Compacted { NextId = state.NextId, Items = state.Items };
}

/// Properties that can be edited on a Todo List item
Expand Down
128 changes: 72 additions & 56 deletions equinox-web-csharp/Web/Controllers/TodosController.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,79 @@
namespace TodoBackend.Controllers

open Microsoft.AspNetCore.Mvc
open TodoBackend

type FromClientIdHeaderAttribute() = inherit FromHeaderAttribute(Name="COMPLETELY_INSECURE_CLIENT_ID")

type TodoView =
{ id: int
url: string
order: int; title: string; completed: bool }

type GetByIdArgsTemplate = { id: int }

// Fulfills contract dictated by https://www.todobackend.com
// To run:
// & dotnet run -f netcoreapp2.1 -p Web
// https://www.todobackend.com/client/index.html?https://localhost:5001/todos
// # NB Jet does now own, control or audit https://todobackend.com; it is a third party site; please satisfy yourself that this is a safe thing use in your environment before using it._
// See also similar backends used as references when implementing:
// https://github.com/ChristianAlexander/dotnetcore-todo-webapi/blob/master/src/TodoWebApi/Controllers/TodosController.cs
// https://github.com/joeaudette/playground/blob/master/spa-stack/src/FSharp.WebLib/Controllers.fs
[<Route "[controller]"; ApiController>]
type TodosController(service: Todo.Service) =
inherit ControllerBase()

let toProps (value : TodoView) : Todo.Props = { order = value.order; title = value.title; completed = value.completed }

member private __.WithUri(x : Todo.View) : TodoView =
let url = __.Url.RouteUrl("GetTodo", { id=x.id }, __.Request.Scheme) // Supplying scheme is secret sauce for making it absolute as required by client
{ id = x.id; url = url; order = x.order; title = x.title; completed = x.completed }

[<HttpGet>]
member __.Get([<FromClientIdHeader>]clientId : ClientId) = async {
let! xs = service.List(clientId)
return seq { for x in xs -> __.WithUri(x) }
}
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

[<HttpGet("{id}", Name="GetTodo")>]
member __.Get([<FromClientIdHeader>]clientId : ClientId, id) : Async<IActionResult> = async {
let! x = service.TryGet(clientId, id)
return match x with None -> __.NotFound() :> _ | Some x -> ObjectResult(__.WithUri x) :> _
namespace TodoBackendTemplate.Controllers
{
public class FromClientIdHeaderAttribute : FromHeaderAttribute
{
public FromClientIdHeaderAttribute()
{
Name = "COMPLETELY_INSECURE_CLIENT_ID";
}
}

[<HttpPost>]
member __.Post([<FromClientIdHeader>]clientId : ClientId, [<FromBody>]value : TodoView) : Async<TodoView> = async {
let! created = service.Create(clientId, toProps value)
return __.WithUri created
public class TodoView
{
public int Id { get; set; }
public string Url { get; set; }
public int Order { get; set; }
public string Title { get; set; }
public bool Completed { get; set; }
}

[<HttpPatch "{id}">]
member __.Patch([<FromClientIdHeader>]clientId : ClientId, id, [<FromBody>]value : TodoView) : Async<TodoView> = async {
let! updated = service.Patch(clientId, id, toProps value)
return __.WithUri updated
}
// Fulfills contract dictated by https://www.todobackend.com
// To run:
// & dotnet run -f netcoreapp2.1 -p Web
// https://www.todobackend.com/client/index.html?https://localhost:5001/todos
// # NB Jet does now own, control or audit https://todobackend.com; it is a third party site; please satisfy yourself that this is a safe thing use in your environment before using it._
// See also similar backends used as references when implementing:
// https://github.com/ChristianAlexander/dotnetcore-todo-webapi/blob/master/src/TodoWebApi/Controllers/TodosController.cs
// https://github.com/joeaudette/playground/blob/master/spa-stack/src/FSharp.WebLib/Controllers.fs
[Route("[controller]"), ApiController]
public class TodosController : ControllerBase
{
private readonly Todo.Service _service;

public TodosController(Todo.Service service) =>
_service = service;

private Todo.Props ToProps(TodoView value) =>
new Todo.Props {Order = value.Order, Title = value.Title, Completed = value.Completed};

private TodoView WithUri(Todo.View x)
{
// Supplying scheme is secret sauce for making it absolute as required by client
var url = Url.RouteUrl("GetTodo", new {id = x.Id}, Request.Scheme);
return new TodoView {Id = x.Id, Url = url, Order = x.Order, Title = x.Title, Completed = x.Completed};
}

[<HttpDelete "{id}">]
member __.Delete([<FromClientIdHeader>]clientId : ClientId, id): Async<unit> =
service.Execute(clientId, Todo.Commands.Delete id)
[HttpGet]
public async Task<IEnumerable<TodoView>> Get([FromClientIdHeader] ClientId clientId) =>
from x in await _service.List(clientId) select WithUri(x);

[<HttpDelete>]
member __.DeleteAll([<FromClientIdHeader>]clientId : ClientId): Async<unit> =
service.Execute(clientId, Todo.Commands.Clear)
[HttpGet("{id}", Name = "GetTodo")]
public async Task<IActionResult> Get([FromClientIdHeader] ClientId clientId, int id)
{
var res = await _service.TryGet(clientId, id);
if (res == null) return NotFound();
return new ObjectResult(WithUri(res));
}

[HttpPost]
public async Task<TodoView> Post([FromClientIdHeader] ClientId clientId, [FromBody] TodoView value) =>
WithUri(await _service.Create(clientId, ToProps(value)));

[HttpPatch("{id}")]
public async Task<TodoView> Patch([FromClientIdHeader] ClientId clientId, int id, [FromBody] TodoView value) =>
WithUri(await _service.Patch(clientId, id, ToProps(value)));

[HttpDelete("{id}")]
public Task Delete([FromClientIdHeader] ClientId clientId, int id) =>
_service.Execute(clientId, new Todo.Commands.Delete {Id = id});

[HttpDelete]
public Task DeleteAll([FromClientIdHeader] ClientId clientId) =>
_service.Execute(clientId, new Todo.Commands.Clear());
}
}
37 changes: 16 additions & 21 deletions equinox-web-csharp/Web/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
#define cosmos
#define eventStore
#define memoryStore
#define todos
#define aggregate
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Builder;
Expand Down Expand Up @@ -49,7 +44,7 @@ private static void ConfigureServices(IServiceCollection services, EquinoxContex
});
services.AddSingleton(sp => new ServiceBuilder(context, Serilog.Log.ForContext<EquinoxContext>()));
#if todos
services.AddSingleton(sp => sp.GetRequiredService<ServiceBuilder>().CreateTodosService());
services.AddSingleton(sp => sp.GetRequiredService<ServiceBuilder>().CreateTodoService());
#endif
#if aggregate
services.AddSingleton(sp => sp.GetRequiredService<ServiceBuilder>().CreateAggregateService());
Expand Down Expand Up @@ -120,14 +115,14 @@ public ServiceBuilder(EquinoxContext context, ILogger handlerLog)

#if todos
public Todo.Service CreateTodoService() =>
Todo.Service(
new Todo.Service(
_handlerLog,
_context.Resolve(
EquinoxCodec.Create<Todo.Events.Event>(),
Todo.Folds.fold,
Todo.Folds.initial,
Todo.Folds.isOrigin,
Todo.Folds.compact));
EquinoxCodec.Create(Todo.Events.Encode,Todo.Events.TryDecode),
Todo.Folds.Fold,
Todo.Folds.Initial,
Todo.Folds.IsOrigin,
Todo.Folds.Compact));
#endif
#if aggregate
public Aggregate.Service CreateAggregateService() =>
Expand All @@ -141,15 +136,15 @@ public Aggregate.Service CreateAggregateService() =>
Aggregate.Folds.Compact));
#endif
#if (!aggregate && !todos)
public Thing.Service CreateAggregateService() =>
Aggregate.Service(
_handlerLog,
_context.Resolve(
EquinoxCodec.Create<Thing.Events.Event>(),
Thing.Folds.fold,
Thing.Folds.initial,
Thing.Folds.isOrigin,
Thing.Folds.compact));
// public Thing.Service CreateAggregateService() =>
// Aggregate.Service(
// _handlerLog,
// _context.Resolve(
// EquinoxCodec.Create<Thing.Events.Event>(),
// Thing.Folds.fold,
// Thing.Folds.initial,
// Thing.Folds.isOrigin,
// Thing.Folds.compact));
#endif
}
}
2 changes: 1 addition & 1 deletion equinox-web-csharp/Web/Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG;NETCOREAPP;NETCOREAPP2_1;eventStore;cosmos;todos;aggregate</DefineConstants>
<DefineConstants>TRACE;memoryStore;todos;aggregate</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit 41691fa

Please sign in to comment.