Browse Source

Merge pull request #9 from dotnet-architecture/dev

PR to merge into skynode dev branch
pull/391/head
Dexter Valkyrie 7 years ago
committed by GitHub
parent
commit
52e0a4e3b3
16 changed files with 180 additions and 33 deletions
  1. +3
    -2
      docker-compose.yml
  2. +2
    -2
      src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/AzureHealthCheckBuilderExtensions.cs
  3. +1
    -8
      src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  4. +9
    -0
      src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs
  5. +30
    -0
      src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs
  6. +16
    -2
      src/Services/Basket/Basket.API/Model/BasketItem.cs
  7. +1
    -0
      src/Services/Basket/Basket.API/Startup.cs
  8. +7
    -1
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  9. +5
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs
  10. +11
    -4
      src/Web/WebSPA/Client/modules/basket/basket.component.html
  11. +28
    -9
      src/Web/WebSPA/Client/modules/basket/basket.component.ts
  12. +2
    -2
      src/Web/WebSPA/Client/modules/orders/orders.component.ts
  13. +3
    -2
      src/Web/WebSPA/Client/modules/shared/services/basket.wrapper.service.ts
  14. +1
    -1
      src/Web/WebSPA/Client/modules/shared/services/data.service.ts
  15. +47
    -0
      test/Services/UnitTest/Ordering/Builders.cs
  16. +14
    -0
      test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs

+ 3
- 2
docker-compose.yml View File

@ -115,8 +115,9 @@ services:
image: redis image: redis
ports: ports:
- "6379:6379" - "6379:6379"
rabbitmq: rabbitmq:
image: rabbitmq
image: rabbitmq:3-management
ports: ports:
- "15672:15672"
- "5672:5672" - "5672:5672"

+ 2
- 2
src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/AzureHealthCheckBuilderExtensions.cs View File

@ -70,7 +70,7 @@ namespace Microsoft.Extensions.HealthChecks
var properties = await tableClient.GetServicePropertiesAsync().ConfigureAwait(false); var properties = await tableClient.GetServicePropertiesAsync().ConfigureAwait(false);
if (String.IsNullOrWhiteSpace(tableName))
if (!String.IsNullOrWhiteSpace(tableName))
{ {
var table = tableClient.GetTableReference(tableName); var table = tableClient.GetTableReference(tableName);
@ -150,7 +150,7 @@ namespace Microsoft.Extensions.HealthChecks
var properties = await queueClient.GetServicePropertiesAsync().ConfigureAwait(false); var properties = await queueClient.GetServicePropertiesAsync().ConfigureAwait(false);
if (String.IsNullOrWhiteSpace(queueName))
if (!String.IsNullOrWhiteSpace(queueName))
{ {
var queue = queueClient.GetQueueReference(queueName); var queue = queueClient.GetQueueReference(queueName);


+ 1
- 8
src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -8,7 +8,7 @@ using System.Net;
namespace Basket.API.Infrastructure.Filters namespace Basket.API.Infrastructure.Filters
{ {
public class HttpGlobalExceptionFilter : IExceptionFilter
public partial class HttpGlobalExceptionFilter : IExceptionFilter
{ {
private readonly IHostingEnvironment env; private readonly IHostingEnvironment env;
private readonly ILogger<HttpGlobalExceptionFilter> logger; private readonly ILogger<HttpGlobalExceptionFilter> logger;
@ -52,12 +52,5 @@ namespace Basket.API.Infrastructure.Filters
} }
context.ExceptionHandled = true; context.ExceptionHandled = true;
} }
private class JsonErrorResponse
{
public string[] Messages { get; set; }
public object DeveloperMessage { get; set; }
}
} }
} }

+ 9
- 0
src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs View File

@ -0,0 +1,9 @@
namespace Basket.API.Infrastructure.Filters
{
public class JsonErrorResponse
{
public string[] Messages { get; set; }
public object DeveloperMessage { get; set; }
}
}

+ 30
- 0
src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs View File

@ -0,0 +1,30 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Basket.API.Infrastructure.Filters
{
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
{
return;
}
var validationErrors = context.ModelState
.Keys
.SelectMany(k => context.ModelState[k].Errors)
.Select(e => e.ErrorMessage)
.ToArray();
var json = new JsonErrorResponse
{
Messages = validationErrors
};
context.Result = new BadRequestObjectResult(json);
}
}
}

+ 16
- 2
src/Services/Basket/Basket.API/Model/BasketItem.cs View File

@ -1,6 +1,9 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
{ {
public class BasketItem
public class BasketItem : IValidatableObject
{ {
public string Id { get; set; } public string Id { get; set; }
public string ProductId { get; set; } public string ProductId { get; set; }
@ -9,5 +12,16 @@
public decimal OldUnitPrice { get; set; } public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; } public int Quantity { get; set; }
public string PictureUrl { get; set; } public string PictureUrl { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (Quantity < 1)
{
results.Add(new ValidationResult("Invalid number of units", new []{ "Quantity" }));
}
return results;
}
} }
} }

+ 1
- 0
src/Services/Basket/Basket.API/Startup.cs View File

@ -54,6 +54,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddMvc(options => services.AddMvc(options =>
{ {
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); options.Filters.Add(typeof(HttpGlobalExceptionFilter));
options.Filters.Add(typeof(ValidateModelStateFilter));
}).AddControllersAsServices(); }).AddControllersAsServices();
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>


+ 7
- 1
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -72,8 +72,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
if (discount > existingOrderForProduct.GetCurrentDiscount()) if (discount > existingOrderForProduct.GetCurrentDiscount())
{ {
existingOrderForProduct.SetNewDiscount(discount); existingOrderForProduct.SetNewDiscount(discount);
existingOrderForProduct.AddUnits(units);
} }
existingOrderForProduct.AddUnits(units);
} }
else else
{ {
@ -187,6 +188,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
{ {
throw new OrderingDomainException($"Not possible to change order status from {OrderStatus.Name} to {orderStatusToChange.Name}."); throw new OrderingDomainException($"Not possible to change order status from {OrderStatus.Name} to {orderStatusToChange.Name}.");
} }
public decimal GetTotal()
{
return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice());
}
} }
} }

+ 5
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs View File

@ -59,6 +59,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
return _units; return _units;
} }
public decimal GetUnitPrice()
{
return _unitPrice;
}
public string GetOrderItemProductName() => _productName; public string GetOrderItemProductName() => _productName;
public void SetNewDiscount(decimal discount) public void SetNewDiscount(decimal discount)


+ 11
- 4
src/Web/WebSPA/Client/modules/basket/basket.component.html View File

@ -1,7 +1,13 @@
<div class="esh-basket"> <div class="esh-basket">
<esh-header url="/catalog">Back to catalog</esh-header> <esh-header url="/catalog">Back to catalog</esh-header>
<div class="container"> <div class="container">
<div *ngFor="let errorMessage of errorMessages">
<div class="esh-basket-items-margin-left1 row">
<div class="alert alert-warning" role="alert">&nbsp;{{errorMessage}}</div>
</div>
</div>
<article class="esh-basket-titles row"> <article class="esh-basket-titles row">
<section class="esh-basket-title col-xs-3">Product</section> <section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section> <section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
@ -14,16 +20,17 @@
<article class="esh-basket-items row"> <article class="esh-basket-items row">
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down"> <section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="{{item.pictureUrl}}" />
<img class="esh-basket-image" src="{{item.pictureUrl}}"/>
</section> </section>
<section class="esh-basket-item esh-basket-item--middle col-xs-3">{{item.productName}}</section> <section class="esh-basket-item esh-basket-item--middle col-xs-3">{{item.productName}}</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ {{item.unitPrice | number:'.2-2'}}</section> <section class="esh-basket-item esh-basket-item--middle col-xs-2">$ {{item.unitPrice | number:'.2-2'}}</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2"> <section class="esh-basket-item esh-basket-item--middle col-xs-2">
<input class="esh-basket-input"
<input id="quantity"
class="esh-basket-input"
type="number" type="number"
min="1" min="1"
[(ngModel)]="item.quantity" [(ngModel)]="item.quantity"
(change)="itemQuantityChanged(item)" />
(change)="itemQuantityChanged(item)"/>
</section> </section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ {{(item.unitPrice * item.quantity) | number:'.2-2'}}</section> <section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ {{(item.unitPrice * item.quantity) | number:'.2-2'}}</section>
</article> </article>


+ 28
- 9
src/Web/WebSPA/Client/modules/basket/basket.component.ts View File

@ -1,9 +1,13 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { BasketService } from './basket.service';
import { IBasket } from '../shared/models/basket.model';
import { IBasketItem } from '../shared/models/basketItem.model';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import { BasketService } from './basket.service';
import { IBasket } from '../shared/models/basket.model';
import { IBasketItem } from '../shared/models/basketItem.model';
import { BasketWrapperService } from '../shared/services/basket.wrapper.service'; import { BasketWrapperService } from '../shared/services/basket.wrapper.service';
@Component({ @Component({
@ -12,6 +16,7 @@ import { BasketWrapperService } from '../shared/services/basket.wrapper.service'
templateUrl: './basket.component.html' templateUrl: './basket.component.html'
}) })
export class BasketComponent implements OnInit { export class BasketComponent implements OnInit {
errorMessages: any;
basket: IBasket; basket: IBasket;
totalPrice: number = 0; totalPrice: number = 0;
@ -29,13 +34,27 @@ export class BasketComponent implements OnInit {
this.service.setBasket(this.basket).subscribe(x => console.log('basket updated: ' + x)); this.service.setBasket(this.basket).subscribe(x => console.log('basket updated: ' + x));
} }
update(event: any) {
this.service.setBasket(this.basket).subscribe(x => console.log('basket updated: ' + x));
update(event: any): Observable<boolean> {
let setBasketObservable = this.service.setBasket(this.basket);
setBasketObservable
.subscribe(
x => {
this.errorMessages = [];
console.log('basket updated: ' + x);
},
errMessage => this.errorMessages = errMessage.messages);
return setBasketObservable;
} }
checkOut(event: any) { checkOut(event: any) {
this.basketwrapper.basket = this.basket;
this.router.navigate(['order']);
this.update(event)
.subscribe(
x => {
this.errorMessages = [];
this.basketwrapper.basket = this.basket;
this.router.navigate(['order'],
errMessage => this.errorMessages = errMessage.messages);
});
} }
private calculateTotalPrice() { private calculateTotalPrice() {


+ 2
- 2
src/Web/WebSPA/Client/modules/orders/orders.component.ts View File

@ -20,7 +20,7 @@ export class OrdersComponent implements OnInit {
ngOnInit() { ngOnInit() {
if (this.configurationService.isReady) { if (this.configurationService.isReady) {
this.getOrders()
this.getOrders();
} else { } else {
this.configurationService.settingsLoaded$.subscribe(x => { this.configurationService.settingsLoaded$.subscribe(x => {
this.getOrders(); this.getOrders();
@ -31,7 +31,7 @@ export class OrdersComponent implements OnInit {
this.interval = setTimeout(() => { this.interval = setTimeout(() => {
this.service.getOrders().subscribe(orders => { this.service.getOrders().subscribe(orders => {
this.orders = orders; this.orders = orders;
if (this.orders.length != this.oldOrders.length) {
if (this.orders.length !== this.oldOrders.length) {
clearInterval(this.interval); clearInterval(this.interval);
} }
}); });


+ 3
- 2
src/Web/WebSPA/Client/modules/shared/services/basket.wrapper.service.ts View File

@ -4,7 +4,8 @@ import { Subject } from 'rxjs/Subject';
import { ICatalogItem } from '../models/catalogItem.model'; import { ICatalogItem } from '../models/catalogItem.model';
import { IBasketItem } from '../models/basketItem.model'; import { IBasketItem } from '../models/basketItem.model';
import { IBasket } from '../models/basket.model'; import { IBasket } from '../models/basket.model';
import { SecurityService } from '../services/security.service';
import { SecurityService } from '../services/security.service';
import { Guid } from '../../../guid';
@Injectable() @Injectable()
export class BasketWrapperService { export class BasketWrapperService {
@ -27,7 +28,7 @@ export class BasketWrapperService {
productName: item.name, productName: item.name,
quantity: 1, quantity: 1,
unitPrice: item.price, unitPrice: item.price,
id: '',
id: Guid.newGuid(),
oldUnitPrice: 0 oldUnitPrice: 0
}; };


+ 1
- 1
src/Web/WebSPA/Client/modules/shared/services/data.service.ts View File

@ -102,7 +102,7 @@ export class DataService {
if (error instanceof Response) { if (error instanceof Response) {
let errMessage = ''; let errMessage = '';
try { try {
errMessage = error.json().error;
errMessage = error.json();
} catch (err) { } catch (err) {
errMessage = error.statusText; errMessage = error.statusText;
} }


+ 47
- 0
test/Services/UnitTest/Ordering/Builders.cs View File

@ -0,0 +1,47 @@
using System;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
namespace UnitTest.Ordering
{
public class AddressBuilder
{
public Address Build()
{
return new Address("street", "city", "state", "country", "zipcode");
}
}
public class OrderBuilder
{
private readonly Order order;
public OrderBuilder(Address address)
{
order = new Order(
"userId",
address,
cardTypeId:5,
cardNumber:"12",
cardSecurityNumber:"123",
cardHolderName:"name",
cardExpiration:DateTime.UtcNow);
}
public OrderBuilder AddOne(
int productId,
string productName,
decimal unitPrice,
decimal discount,
string pictureUrl,
int units = 1)
{
order.AddOrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
return this;
}
public Order Build()
{
return order;
}
}
}

+ 14
- 0
test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs View File

@ -2,6 +2,8 @@
using Ordering.Domain.Events; using Ordering.Domain.Events;
using Ordering.Domain.Exceptions; using Ordering.Domain.Exceptions;
using System; using System;
using System.Linq;
using UnitTest.Ordering;
using Xunit; using Xunit;
public class OrderAggregateTest public class OrderAggregateTest
@ -93,6 +95,18 @@ public class OrderAggregateTest
Assert.Throws<OrderingDomainException>(() => fakeOrderItem.AddUnits(-1)); Assert.Throws<OrderingDomainException>(() => fakeOrderItem.AddUnits(-1));
} }
[Fact]
public void when_add_two_times_on_the_same_item_then_the_total_of_order_should_be_the_sum_of_the_two_items()
{
var address = new AddressBuilder().Build();
var order = new OrderBuilder(address)
.AddOne(1,"cup",10.0m,0,string.Empty)
.AddOne(1,"cup",10.0m,0,string.Empty)
.Build();
Assert.Equal(20.0m, order.GetTotal());
}
[Fact] [Fact]
public void Add_new_Order_raises_new_event() public void Add_new_Order_raises_new_event()
{ {


Loading…
Cancel
Save