Skip to content

Commit

Permalink
refactor: ♻️ refactor project folder structure and improve tests (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdihadeli authored Aug 22, 2024
1 parent 411f98e commit ea0fa46
Show file tree
Hide file tree
Showing 266 changed files with 1,299 additions and 966 deletions.
65 changes: 37 additions & 28 deletions Vertical.Slice.Template.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template.Con
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template.TestsShared", "tests\Vertical.Slice.Template.TestsShared\Vertical.Slice.Template.TestsShared.csproj", "{E3C9B67C-8443-49C9-A6F3-EA099199F66F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template.Shared", "src\Vertical.Slice.Template.Shared\Vertical.Slice.Template.Shared.csproj", "{05B80842-E143-46CA-A881-CF14A85D927C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template.ApiClient", "src\Vertical.Slice.Template.ApiClient\Vertical.Slice.Template.ApiClient.csproj", "{51C30F5B-FC3F-41C2-BD7C-FA254B956C55}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution-items", "solution-items", "{798579C1-7DEC-47A2-9C18-CA3DBE4A6573}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Expand Down Expand Up @@ -72,10 +68,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\publish.yml = .github\workflows\publish.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template", "src\Vertical.Slice.Template\Vertical.Slice.Template.csproj", "{31F7A4C7-66DD-4387-9981-4A64501018E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template.Api", "src\Vertical.Slice.Template.Api\Vertical.Slice.Template.Api.csproj", "{F45584B2-2831-410B-BC9D-3E13817E768F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".devcontainer", ".devcontainer", "{268E2B03-D9F4-4D31-84BA-6D1DC250E894}"
ProjectSection(SolutionItems) = preProject
.devcontainer\devcontainer.json = .devcontainer\devcontainer.json
Expand All @@ -98,6 +90,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker-compose", "docker-co
deployments\docker-compose\docker-compose.infrastructure.yaml = deployments\docker-compose\docker-compose.infrastructure.yaml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiClients", "ApiClients", "{66D6E224-01FF-4D8F-98C1-CEA3CD2D5F15}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{AF80152C-AF0D-475E-AD69-A7867D1ACA26}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{54C91A04-E128-4ADB-9B49-E0FEAC783069}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiClients", "src\ApiClients\ApiClients.csproj", "{56A79202-F0BB-4BDB-B042-B773EDBCDFE3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template", "src\App\Vertical.Slice.Template\Vertical.Slice.Template.csproj", "{AC478225-5EA9-4895-875A-0B01217C4576}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vertical.Slice.Template.Api", "src\App\Vertical.Slice.Template.Api\Vertical.Slice.Template.Api.csproj", "{23F3C3DD-6630-4743-BE50-9BD6F719665E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "src\Shared\Shared.csproj", "{A5FB3B9E-FF0A-45F4-8D37-080C770A5AB4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -119,14 +125,17 @@ Global
{A79D441C-D450-474E-9CFC-008335A80B13} = {C4B80800-5430-4D4E-B473-261157D54CD4}
{AEFEEF63-5831-49E5-A7D9-892AF653C32D} = {798579C1-7DEC-47A2-9C18-CA3DBE4A6573}
{ED6C6F59-8A39-4D7D-BB93-888AA486AFE9} = {AEFEEF63-5831-49E5-A7D9-892AF653C32D}
{51C30F5B-FC3F-41C2-BD7C-FA254B956C55} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{05B80842-E143-46CA-A881-CF14A85D927C} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{31F7A4C7-66DD-4387-9981-4A64501018E7} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{F45584B2-2831-410B-BC9D-3E13817E768F} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{268E2B03-D9F4-4D31-84BA-6D1DC250E894} = {798579C1-7DEC-47A2-9C18-CA3DBE4A6573}
{3A68A53A-1E76-49DE-A622-569267333288} = {268E2B03-D9F4-4D31-84BA-6D1DC250E894}
{9DF99E09-AEC5-4F44-BC7E-CB16EF796431} = {798579C1-7DEC-47A2-9C18-CA3DBE4A6573}
{F3FCF437-0EF6-4149-AA20-46C787B6FCB1} = {9DF99E09-AEC5-4F44-BC7E-CB16EF796431}
{66D6E224-01FF-4D8F-98C1-CEA3CD2D5F15} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{AF80152C-AF0D-475E-AD69-A7867D1ACA26} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{54C91A04-E128-4ADB-9B49-E0FEAC783069} = {9BB0311A-FE23-4C8D-B7C0-E8CD49FA3D20}
{56A79202-F0BB-4BDB-B042-B773EDBCDFE3} = {66D6E224-01FF-4D8F-98C1-CEA3CD2D5F15}
{AC478225-5EA9-4895-875A-0B01217C4576} = {AF80152C-AF0D-475E-AD69-A7867D1ACA26}
{23F3C3DD-6630-4743-BE50-9BD6F719665E} = {AF80152C-AF0D-475E-AD69-A7867D1ACA26}
{A5FB3B9E-FF0A-45F4-8D37-080C770A5AB4} = {54C91A04-E128-4ADB-9B49-E0FEAC783069}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5F41BF51-E13B-48BF-8D81-874FBA9BC961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand All @@ -149,14 +158,6 @@ Global
{E3C9B67C-8443-49C9-A6F3-EA099199F66F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3C9B67C-8443-49C9-A6F3-EA099199F66F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3C9B67C-8443-49C9-A6F3-EA099199F66F}.Release|Any CPU.Build.0 = Release|Any CPU
{05B80842-E143-46CA-A881-CF14A85D927C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05B80842-E143-46CA-A881-CF14A85D927C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05B80842-E143-46CA-A881-CF14A85D927C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05B80842-E143-46CA-A881-CF14A85D927C}.Release|Any CPU.Build.0 = Release|Any CPU
{51C30F5B-FC3F-41C2-BD7C-FA254B956C55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51C30F5B-FC3F-41C2-BD7C-FA254B956C55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51C30F5B-FC3F-41C2-BD7C-FA254B956C55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51C30F5B-FC3F-41C2-BD7C-FA254B956C55}.Release|Any CPU.Build.0 = Release|Any CPU
{4E185D04-8B8D-4BE9-AAE3-5406C703DF83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E185D04-8B8D-4BE9-AAE3-5406C703DF83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E185D04-8B8D-4BE9-AAE3-5406C703DF83}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -165,13 +166,21 @@ Global
{A79D441C-D450-474E-9CFC-008335A80B13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A79D441C-D450-474E-9CFC-008335A80B13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A79D441C-D450-474E-9CFC-008335A80B13}.Release|Any CPU.Build.0 = Release|Any CPU
{31F7A4C7-66DD-4387-9981-4A64501018E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31F7A4C7-66DD-4387-9981-4A64501018E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31F7A4C7-66DD-4387-9981-4A64501018E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31F7A4C7-66DD-4387-9981-4A64501018E7}.Release|Any CPU.Build.0 = Release|Any CPU
{F45584B2-2831-410B-BC9D-3E13817E768F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F45584B2-2831-410B-BC9D-3E13817E768F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F45584B2-2831-410B-BC9D-3E13817E768F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F45584B2-2831-410B-BC9D-3E13817E768F}.Release|Any CPU.Build.0 = Release|Any CPU
{56A79202-F0BB-4BDB-B042-B773EDBCDFE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{56A79202-F0BB-4BDB-B042-B773EDBCDFE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56A79202-F0BB-4BDB-B042-B773EDBCDFE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56A79202-F0BB-4BDB-B042-B773EDBCDFE3}.Release|Any CPU.Build.0 = Release|Any CPU
{AC478225-5EA9-4895-875A-0B01217C4576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC478225-5EA9-4895-875A-0B01217C4576}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC478225-5EA9-4895-875A-0B01217C4576}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC478225-5EA9-4895-875A-0B01217C4576}.Release|Any CPU.Build.0 = Release|Any CPU
{23F3C3DD-6630-4743-BE50-9BD6F719665E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23F3C3DD-6630-4743-BE50-9BD6F719665E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23F3C3DD-6630-4743-BE50-9BD6F719665E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23F3C3DD-6630-4743-BE50-9BD6F719665E}.Release|Any CPU.Build.0 = Release|Any CPU
{A5FB3B9E-FF0A-45F4-8D37-080C770A5AB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5FB3B9E-FF0A-45F4-8D37-080C770A5AB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5FB3B9E-FF0A-45F4-8D37-080C770A5AB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5FB3B9E-FF0A-45F4-8D37-080C770A5AB4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace Vertical.Slice.Template.Api;

public class CatalogsApiMetadata { }
public class CatalogsApiMetadata;
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using System.Reflection;
using Serilog;
using Serilog.Events;
using Shared.Core.Extensions.ServiceCollectionsExtensions;
using Shared.Logging;
using Shared.Swagger;
using Shared.Web.Minimal.Extensions;
using Vertical.Slice.Template;
using Vertical.Slice.Template.Shared;
using Vertical.Slice.Template.Shared.Core.Extensions.ServiceCollectionsExtensions;
using Vertical.Slice.Template.Shared.Extensions.WebApplicationBuilderExtensions;
using Vertical.Slice.Template.Shared.Logging;
using Vertical.Slice.Template.Shared.Swagger;
using Vertical.Slice.Template.Shared.Web.Minimal.Extensions;

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization
Log.Logger = new LoggerConfiguration()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@
<ProjectReference Include="..\Vertical.Slice.Template\Vertical.Slice.Template.csproj" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Vertical.Slice.Template.DependencyTests" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Humanizer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Shared.EF;
using Vertical.Slice.Template.Products.Models;
using Vertical.Slice.Template.Shared.Data;
using Vertical.Slice.Template.Shared.EF;

namespace Vertical.Slice.Template.Products.Data.Configurations;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
using Shared.Abstractions.Core.CQRS;
using Shared.Abstractions.Persistence.Ef;
using Shared.Core.Extensions;
using Shared.Core.Id;
using Shared.EF.Extensions;
using Shared.Validation.Extensions;
using Vertical.Slice.Template.Products.Models;
using Vertical.Slice.Template.Shared.Abstractions.Core.CQRS;
using Vertical.Slice.Template.Shared.Abstractions.Ef;
using Vertical.Slice.Template.Shared.Core.Extensions;
using Vertical.Slice.Template.Shared.Core.Id;
using Vertical.Slice.Template.Shared.Data;
using Vertical.Slice.Template.Shared.EF.Extensions;
using Vertical.Slice.Template.Shared.Validation.Extensions;

namespace Vertical.Slice.Template.Products.Features.CreatingProduct.v1;

Expand All @@ -20,7 +20,7 @@ namespace Vertical.Slice.Template.Products.Features.CreatingProduct.v1;
// https://codeopinion.com/leaking-value-objects-from-your-domain/
// https://www.youtube.com/watch?v=CdanF8PWJng
// we don't pass value-objects and domains to our commands and events, just primitive types
public record CreateProduct(string Name, Guid CategoryId, decimal Price, string? Description = null)
internal record CreateProduct(string Name, Guid CategoryId, decimal Price, string? Description = null)
: ICommand<CreateProductResult>
{
public Guid Id { get; } = IdGenerator.NewId();
Expand Down Expand Up @@ -50,38 +50,25 @@ public CreateProductValidator()
}
}

internal class CreateProductHandler : ICommandHandler<CreateProduct, CreateProductResult>
internal class CreateProductHandler(
DbExecuters.CreateAndSaveProductExecutor createAndSaveProductExecutor,
IMapper mapper,
IMediator mediator,
ILogger<CreateProductHandler> logger
) : ICommandHandler<CreateProduct, CreateProductResult>
{
private readonly DbExecuters.CreateAndSaveProductExecutor _createAndSaveProductExecutor;
private readonly IMapper _mapper;
private readonly IMediator _mediator;
private readonly ILogger<CreateProductHandler> _logger;

public CreateProductHandler(
DbExecuters.CreateAndSaveProductExecutor createAndSaveProductExecutor,
IMapper mapper,
IMediator mediator,
ILogger<CreateProductHandler> logger
)
{
_createAndSaveProductExecutor = createAndSaveProductExecutor;
_mapper = mapper;
_mediator = mediator;
_logger = logger;
}

public async Task<CreateProductResult> Handle(CreateProduct request, CancellationToken cancellationToken)
{
request.NotBeNull();

var product = _mapper.Map<Product>(request);
var product = mapper.Map<Product>(request);

await _createAndSaveProductExecutor(product, cancellationToken);
await createAndSaveProductExecutor(product, cancellationToken);

var (name, categoryId, price, description) = request;
await _mediator.Publish(ProductCreated.Of(request.Id, name, categoryId, price, description), cancellationToken);
await mediator.Publish(ProductCreated.Of(request.Id, name, categoryId, price, description), cancellationToken);

_logger.LogInformation("Product a with ID: '{ProductId} created.'", request.Id);
logger.LogInformation("Product a with ID: '{ProductId} created.'", request.Id);

return new CreateProductResult(product.Id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Vertical.Slice.Template.Shared.Abstractions.Web;
using Vertical.Slice.Template.Shared.Web.Minimal.Extensions;
using Vertical.Slice.Template.Shared.Web.ProblemDetail.HttpResults;
using Shared.Abstractions.Web;
using Shared.Web.Minimal.Extensions;
using Shared.Web.ProblemDetail.HttpResults;

namespace Vertical.Slice.Template.Products.Features.CreatingProduct.v1;

Expand Down Expand Up @@ -65,4 +65,4 @@ CancellationToken CancellationToken
internal record CreateProductResponse(Guid Id);

// we can expect any value from the user for all reference types are nullable and we should do some validation in other levels (we use pure records mostly for dtos without needing validation)
public record CreateProductRequest(string? Name, Guid CategoryId, decimal Price, string? Description);
internal record CreateProductRequest(string? Name, Guid CategoryId, decimal Price, string? Description);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using FluentValidation;
using Vertical.Slice.Template.Shared.Core.Domain.Events;
using Vertical.Slice.Template.Shared.Validation.Extensions;
using Shared.Core.Domain.Events;
using Shared.Validation.Extensions;

namespace Vertical.Slice.Template.Products.Features.CreatingProduct.v1;

Expand All @@ -11,7 +11,7 @@ namespace Vertical.Slice.Template.Products.Features.CreatingProduct.v1;
// https://codeopinion.com/leaking-value-objects-from-your-domain/
// https://www.youtube.com/watch?v=CdanF8PWJng
// we don't pass value-objects and domains to our commands and events, just primitive types
public record ProductCreated(Guid Id, string Name, Guid CategoryId, decimal Price, string? Description = null)
internal record ProductCreated(Guid Id, string Name, Guid CategoryId, decimal Price, string? Description = null)
: DomainEvent
{
public static ProductCreated Of(Guid id, string? name, Guid categoryId, decimal price, string? description = null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
using AutoMapper;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Shared.Abstractions.Core.CQRS;
using Shared.Abstractions.Persistence.Ef;
using Shared.Cache;
using Shared.Core.Exceptions;
using Shared.Core.Extensions;
using Shared.EF.Extensions;
using Shared.Validation.Extensions;
using Vertical.Slice.Template.Products.Dtos.v1;
using Vertical.Slice.Template.Products.Models;
using Vertical.Slice.Template.Products.ReadModel;
using Vertical.Slice.Template.Shared.Abstractions.Core.CQRS;
using Vertical.Slice.Template.Shared.Abstractions.Ef;
using Vertical.Slice.Template.Shared.Cache;
using Vertical.Slice.Template.Shared.Core.Exceptions;
using Vertical.Slice.Template.Shared.Core.Extensions;
using Vertical.Slice.Template.Shared.Data;
using Vertical.Slice.Template.Shared.EF.Extensions;
using Vertical.Slice.Template.Shared.Validation.Extensions;

namespace Vertical.Slice.Template.Products.Features.GettingProductById.v1;

public record GetProductById(Guid Id) : CacheQuery<GetProductById, GetProductByIdResult>
internal record GetProductById(Guid Id) : CacheQuery<GetProductById, GetProductByIdResult>
{
/// <summary>
/// GetProductById query with validation.
Expand All @@ -41,29 +41,21 @@ public GetProductByIdValidator()
}
}

internal class GetProductByIdHandler : IQueryHandler<GetProductById, GetProductByIdResult>
internal class GetProductByIdHandler(DbExecutors.GetProductByIdExecutor getProductByIdExecutor, IMapper mapper)
: IQueryHandler<GetProductById, GetProductByIdResult>
{
private readonly DbExecutors.GetProductByIdExecutor _getProductByIdExecutor;
private readonly IMapper _mapper;

public GetProductByIdHandler(DbExecutors.GetProductByIdExecutor getProductByIdExecutor, IMapper mapper)
{
_getProductByIdExecutor = getProductByIdExecutor;
_mapper = mapper;
}

public async Task<GetProductByIdResult> Handle(GetProductById request, CancellationToken cancellationToken)
{
request.NotBeNull();

var productReadModel = await _getProductByIdExecutor(request.Id, cancellationToken);
var productReadModel = await getProductByIdExecutor(request.Id, cancellationToken);

if (productReadModel is null)
{
throw new NotFoundException($"product with id {request.Id} not found");
}

var productDto = _mapper.Map<ProductDto>(productReadModel);
var productDto = mapper.Map<ProductDto>(productReadModel);

return new GetProductByIdResult(productDto);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Vertical.Slice.Template.Products.Dtos;
using Shared.Abstractions.Web;
using Shared.Web.Minimal.Extensions;
using Shared.Web.ProblemDetail.HttpResults;
using Vertical.Slice.Template.Products.Dtos.v1;
using Vertical.Slice.Template.Shared.Abstractions.Web;
using Vertical.Slice.Template.Shared.Web.Minimal.Extensions;
using Vertical.Slice.Template.Shared.Web.ProblemDetail.HttpResults;

namespace Vertical.Slice.Template.Products.Features.GettingProductById.v1;

Expand Down
Loading

0 comments on commit ea0fa46

Please sign in to comment.