Skip to content

Commit

Permalink
Catalog - Offers initial (2024-06-21) (#45)
Browse files Browse the repository at this point in the history
* new terms in the dict! 📖

* scaffold of initial Offer modeling

* chore; namespacing for catalog API endpoints

* offers cmd and qry endpoints, namespace fixing, http file

* moved existing catalog projections to namespace schema

* feat; offer projection
  • Loading branch information
erikshafer authored Jun 22, 2024
1 parent 717c943 commit 402f4f5
Show file tree
Hide file tree
Showing 28 changed files with 423 additions and 57 deletions.
3 changes: 3 additions & 0 deletions EventSourcingEcommerce.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=EventStoreDB/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Eventuous/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MicroPlumberd/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=newegg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shopee/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Newegg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Plumberd/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nullo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=otel/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
46 changes: 46 additions & 0 deletions src/Catalog/Catalog.Api/Catalog.Api.Offers.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# curl -X 'GET'
# 'http://localhost:5252/offers/0190421a-1466-4f1b-8261-a22a528aadae'
# -H 'accept: text/plain'
GET http://localhost:5252/offers/0190421a-1466-4f1b-8261-a22a528aadae
accept: text/plain

###

# curl -X 'POST'
# 'http://localhost:5252/offer/draft'
# -H 'accept: text/plain'
# -H 'Content-Type: application/json'
# -d '{
# "sku": "36606-001",
# "createdBy": "erik"
#}'
POST http://localhost:5252/offer/draft
accept: text/plain
Content-Type: application/json

{
"sku": "36606-001",
"createdBy": "erik"
}

###

# curl -X 'POST'
# 'http://localhost:5252/offer/activate'
# -H 'accept: text/plain'
# -H 'Content-Type: application/json'
# -d '{
# "offerId": "0190421a-1466-4f1b-8261-a22a528aada",
# "activatedBy": "erik"
#}'
POST http://localhost:5252/offer/activate
accept: text/plain
Content-Type: application/json

{
"offerId": "0190421a-1466-4f1b-8261-a22a528aadae",
"activatedBy": "erik"
}

###

33 changes: 33 additions & 0 deletions src/Catalog/Catalog.Api/Commands/Offers/OfferCommandService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Catalog.Offers;
using Ecommerce.Core.Identities;
using Eventuous;
using MongoDB.Bson.Serialization;

namespace Catalog.Api.Commands.Offers;

public class OfferCommandService : CommandService<Offer, OfferState, OfferId>
{
[Obsolete("Obsolete usage of OnNewAsync per Eventuous; use new API instead (TODO)")]
public OfferCommandService(
IAggregateStore store,
Services.IsSkuAvailable isSkuAvailable,
Services.IsUserAuthorized isUserAuthorized,
ICombIdGenerator idGenerator)
: base(store)
{
var generatedId = idGenerator.New();
OnNewAsync<OfferCommands.Draft>(cmd => new OfferId(generatedId),
((offer, cmd, _) => offer.Draft(
generatedId,
cmd.Sku,
DateTimeOffset.Now,
cmd.CreatedBy,
isSkuAvailable,
isUserAuthorized)));

OnExisting<OfferCommands.Activate>(cmd => new OfferId(cmd.OfferId),
((offer, cmd) => offer.Activate(
DateTimeOffset.Now,
cmd.ActivatedBy)));
}
}
14 changes: 14 additions & 0 deletions src/Catalog/Catalog.Api/Commands/Offers/OfferCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Catalog.Api.Commands.Offers;

public class OfferCommands
{
public record Draft(
string Sku,
string CreatedBy
);

public record Activate(
string OfferId,
string ActivatedBy
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Ecommerce.Core.Identities;
using Eventuous;

namespace Catalog.Api.Commands;
namespace Catalog.Api.Commands.Prices;

public class PriceCommandService : CommandService<Price, PriceState, PriceId>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Catalog.Api.Commands;
namespace Catalog.Api.Commands.Prices;

public static class PriceCommands
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using Catalog.Products;
using Ecommerce.Core.Identities;
using Eventuous;
using static Catalog.Api.Commands.ProductCommands;

namespace Catalog.Api.Commands;
namespace Catalog.Api.Commands.Products;

public class ProductCommandService : CommandService<Product, ProductState, ProductId>
{
Expand All @@ -17,7 +16,7 @@ public ProductCommandService(
{
// On<InitializeProduct>(); // TODO use new API instead of obsolete versions

OnNewAsync<DraftWithProvidedId>(cmd => new ProductId(cmd.ProductId),
OnNewAsync<ProductCommands.DraftWithProvidedId>(cmd => new ProductId(cmd.ProductId),
((product, cmd, _) => product.Draft(
cmd.ProductId,
cmd.Sku,
Expand All @@ -31,7 +30,7 @@ public ProductCommandService(
isUserAuthorized)));

var generatedId = idGenerator.New();
OnNewAsync<Draft>(cmd => new ProductId(generatedId),
OnNewAsync<ProductCommands.Draft>(cmd => new ProductId(generatedId),
((product, cmd, _) => product.Draft(
generatedId,
cmd.Sku,
Expand All @@ -44,48 +43,48 @@ public ProductCommandService(
isSkuAvailable,
isUserAuthorized)));

OnExisting<Activate>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.Activate>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.Activate(
DateTimeOffset.Now,
cmd.ActivatedBy)));

OnExisting<Archive>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.Archive>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.Archive(
DateTimeOffset.Now,
cmd.ArchivedBy,
cmd.Reason)));

OnExisting<Cancel>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.Cancel>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.CancelDraft(
DateTimeOffset.Now,
cmd.CancelledBy,
cmd.Reason)));

OnExisting<AdjustDescription>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.AdjustDescription>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.AdjustDescription(
cmd.Description,
DateTimeOffset.Now,
cmd.AdjustedBy)));

OnExisting<AdjustName>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.AdjustName>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.AdjustName(
cmd.Name,
DateTimeOffset.Now,
cmd.AdjustedBy)));

OnExisting<AdjustBrand>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.AdjustBrand>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.AdjustBrand(
cmd.Brand,
DateTimeOffset.Now,
cmd.AdjustedBy)));

OnExisting<TakeMeasurement>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.TakeMeasurement>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.TakeMeasurement(
Measurement.GetName(cmd.Type), // TODO evaluate if this is a good path to take
cmd.Unit,
cmd.Value)));

OnExisting<RemoveMeasurement>(cmd => new ProductId(cmd.ProductId),
OnExisting<ProductCommands.RemoveMeasurement>(cmd => new ProductId(cmd.ProductId),
((product, cmd) => product.RemoveMeasurement(
Measurement.GetName(cmd.Type)))); // TODO evaluate if this is a good path to take;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Catalog.Api.Commands;
namespace Catalog.Api.Commands.Products;

public static class ProductCommands
{
Expand Down
21 changes: 21 additions & 0 deletions src/Catalog/Catalog.Api/HttpApi/Offers/OfferCommandApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Catalog.Api.Commands.Offers;
using Catalog.Offers;
using Eventuous;
using Eventuous.AspNetCore.Web;
using Microsoft.AspNetCore.Mvc;

namespace Catalog.Api.HttpApi.Offers;

[Route("/offer")]
public class OfferCommandApi(ICommandService<Offer> service) : CommandHttpApiBase<Offer>(service)
{
[HttpPost]
[Route("draft")]
public Task<ActionResult<Result>> Draft([FromBody] OfferCommands.Draft cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("activate")]
public Task<ActionResult<Result>> Activate([FromBody] OfferCommands.Activate cmd, CancellationToken ct)
=> Handle(cmd, ct);
}
21 changes: 21 additions & 0 deletions src/Catalog/Catalog.Api/HttpApi/Offers/OfferQueryApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Catalog.Offers;
using Eventuous;
using Microsoft.AspNetCore.Mvc;

namespace Catalog.Api.HttpApi.Offers;

[Route("/offers")]
public class OfferQueryApi : ControllerBase
{
private readonly IAggregateStore _store;

public OfferQueryApi(IAggregateStore store) => _store = store;

[HttpGet]
[Route("{id}")]
public async Task<OfferState> GetProduct(string id, CancellationToken ct)
{
var product = await _store.Load<Offer>(StreamName.For<Offer>(id), ct);
return product.State;
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
using Catalog.Api.Commands.Prices;
using Catalog.Prices;
using Eventuous;
using Eventuous.AspNetCore.Web;
using Microsoft.AspNetCore.Mvc;
using static Catalog.Api.Commands.PriceCommands;

namespace Catalog.Api.HttpApi;
namespace Catalog.Api.HttpApi.Prices;

[Route("/price")]
public class PriceCommandApi(ICommandService<Price> service) : CommandHttpApiBase<Price>(service)
{
[HttpPost]
[Route("initialize")]
public Task<ActionResult<Result>> Draft([FromBody] Initialize cmd, CancellationToken ct)
public Task<ActionResult<Result>> Draft([FromBody] PriceCommands.Initialize cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("activate")]
public Task<ActionResult<Result>> Activate([FromBody] Activate cmd, CancellationToken ct)
public Task<ActionResult<Result>> Activate([FromBody] PriceCommands.Activate cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("deprecate")]
public Task<ActionResult<Result>> Deprecate([FromBody] Deprecate cmd, CancellationToken ct)
public Task<ActionResult<Result>> Deprecate([FromBody] PriceCommands.Deprecate cmd, CancellationToken ct)
=> Handle(cmd, ct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Eventuous;
using Microsoft.AspNetCore.Mvc;

namespace Catalog.Api.HttpApi;
namespace Catalog.Api.HttpApi.Prices;

[Route("/prices")]
public class PriceQueryApi : ControllerBase
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,61 @@
using Catalog.Api.Commands.Products;
using Catalog.Products;
using Eventuous;
using Eventuous.AspNetCore.Web;
using Microsoft.AspNetCore.Mvc;
using static Catalog.Api.Commands.ProductCommands;

namespace Catalog.Api.HttpApi;
namespace Catalog.Api.HttpApi.Products;

[Route("/product")]
public class ProductCommandApi(ICommandService<Product> service) : CommandHttpApiBase<Product>(service)
{
[HttpPost]
[Route("draft-with-id")]
public Task<ActionResult<Result>> Draft([FromBody] DraftWithProvidedId cmd, CancellationToken ct)
public Task<ActionResult<Result>> Draft([FromBody] ProductCommands.DraftWithProvidedId cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("draft")]
public Task<ActionResult<Result>> Draft([FromBody] Draft cmd, CancellationToken ct)
public Task<ActionResult<Result>> Draft([FromBody] ProductCommands.Draft cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("activate")]
public Task<ActionResult<Result>> Activate([FromBody] Activate cmd, CancellationToken ct)
public Task<ActionResult<Result>> Activate([FromBody] ProductCommands.Activate cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("archive")]
public Task<ActionResult<Result>> Archive([FromBody] Archive cmd, CancellationToken ct)
public Task<ActionResult<Result>> Archive([FromBody] ProductCommands.Archive cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("cancel")]
public Task<ActionResult<Result>> Cancel([FromBody] Cancel cmd, CancellationToken ct)
public Task<ActionResult<Result>> Cancel([FromBody] ProductCommands.Cancel cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("adjust-description")]
public Task<ActionResult<Result>> AdjustDescription([FromBody] AdjustDescription cmd, CancellationToken ct)
public Task<ActionResult<Result>> AdjustDescription([FromBody] ProductCommands.AdjustDescription cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("adjust-name")]
public Task<ActionResult<Result>> AdjustName([FromBody] AdjustName cmd, CancellationToken ct)
public Task<ActionResult<Result>> AdjustName([FromBody] ProductCommands.AdjustName cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("adjust-brand")]
public Task<ActionResult<Result>> AdjustBrand([FromBody] AdjustBrand cmd, CancellationToken ct)
public Task<ActionResult<Result>> AdjustBrand([FromBody] ProductCommands.AdjustBrand cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("take-measurement")]
public Task<ActionResult<Result>> TakeMeasurement([FromBody] TakeMeasurement cmd, CancellationToken ct)
public Task<ActionResult<Result>> TakeMeasurement([FromBody] ProductCommands.TakeMeasurement cmd, CancellationToken ct)
=> Handle(cmd, ct);

[HttpPost]
[Route("remove-measurement")]
public Task<ActionResult<Result>> RemoveMeasurement([FromBody] RemoveMeasurement cmd, CancellationToken ct)
public Task<ActionResult<Result>> RemoveMeasurement([FromBody] ProductCommands.RemoveMeasurement cmd, CancellationToken ct)
=> Handle(cmd, ct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Eventuous;
using Microsoft.AspNetCore.Mvc;

namespace Catalog.Api.HttpApi;
namespace Catalog.Api.HttpApi.Products;

[Route("/products")]
public class ProductQueryApi : ControllerBase
Expand Down
15 changes: 15 additions & 0 deletions src/Catalog/Catalog.Api/Queries/Offers/OfferDocument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Eventuous.Projections.MongoDB.Tools;

namespace Catalog.Api.Queries.Offers;

public record OfferDocument : ProjectedDocument
{
public OfferDocument(string Id) : base(Id)
{
}

public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.MinValue;
public string CreatedBy { get; set; } = null!;
public string Sku { get; set; } = null!;
public string Status { get; set; } = null!;
}
Loading

0 comments on commit 402f4f5

Please sign in to comment.