Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,21 @@ protected override Task HandleRequirementAsync(
UserIdRequirement requirement,
string username)
{

var userRole = GetUserRole(context.User);
if (userRole == string.Empty)
{
context.Fail();
return Task.CompletedTask;
}

if (userRole == "admin")
if (userRole != "admin")
{
if (!ValidateUserPermissions(
context.User.Claims.ToList(),
requirement.Requirements.ToList()))
var usernameInClaim = GetUsername(context.User);
if (string.IsNullOrEmpty(usernameInClaim) || username != usernameInClaim)
{
context.Fail();
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}

var usernameInClaim = GetUsername(context.User);
if (username != usernameInClaim)
{
context.Fail();
return Task.CompletedTask;
}

if (!ValidateUserPermissions(
context.User.Claims.ToList(),
requirement.Requirements.ToList()))
context.User.Claims.ToArray(),
requirement.Requirements.ToArray()))
{
context.Fail();
return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
service.AddMarten(options =>
{
options.Connection(postgresConnection);
options.Schema.For<Product>().UseNumericRevisions(true);
options.Schema.For<ProductDocument>().UseNumericRevisions(true);
}).UseLightweightSessions();

service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ public async Task SeedDataBaseAsync(Action<ProductConfiguration>? productConfigu
var products = productConfiguration is null ? GetListOfProducts() : [product.ToDbProduct()];

await using var session = store.LightweightSession();
session.Store<Product>(products);
session.Store<ProductDocument>(products);
await session.SaveChangesAsync();
}

public async Task<IReadOnlyList<Product>> GetAllData()
public async Task<IReadOnlyList<ProductDocument>> GetAllData()
{
await using var session = store.LightweightSession();
var data = await session.Query<Product>().ToListAsync();
var data = await session.Query<ProductDocument>().ToListAsync();
return data;
}

private static IEnumerable<Product> GetListOfProducts()
private static IEnumerable<ProductDocument> GetListOfProducts()
{
return
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public record ProductConfiguration

public static class CreateProductConfiguration
{
public static Product ToDbProduct(this ProductConfiguration configuration)
public static ProductDocument ToDbProduct(this ProductConfiguration configuration)
{
return new Product
return new ProductDocument
{
Id = configuration.Id,
Name = configuration.Name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public async Task CreateProduct_With_Valid_Object_Return_Created(CreateProductRe

result.StatusCode.ShouldBe(HttpStatusCode.Created);
await using var session = apiSpecification.GetDocumentStore().LightweightSession();
var valueInDb = session.Query<Product>().First(x => x.Id == createProductRequest.Id);
var valueInDb = session.Query<ProductDocument>().First(x => x.Id == createProductRequest.Id);
valueInDb.ShouldNotBeNull();
valueInDb.Id.ShouldBe(productId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ public async Task GetProductById_Product_NotFound_Returns_NotFound()

// Act
var result = await _client.GetAsync($"api/v1/catalog/products/{productId}");
var response = await result.Content.ReadFromJsonAsync<ProblemDetails>();
var response = await result.Content.ReadFromJsonAsync<string>();

// Assert
result.StatusCode.ShouldBe(System.Net.HttpStatusCode.NotFound);
result.StatusCode.ShouldBe(HttpStatusCode.NotFound);
response.ShouldNotBeNull();
response.Detail.ShouldNotBeNull();
response.Detail.ShouldBe($"Entity \"Product\" ({productId}) was not found.");
response.ShouldBe(productId.ToString());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public async Task DisposeAsync()
await Task.CompletedTask;
}

private static List<ProductResponse> GetProductsModules(IReadOnlyList<Product> products)
private static List<ProductResponse> GetProductsModules(IReadOnlyList<ProductDocument> products)
{
return products.Select(x => new ProductResponse(
Id: Ulid.Parse(x.Id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ public async Task DisposeAsync()
await Task.CompletedTask;
}

private static ProductModule ToProductModule(Product product)
private static ProductModule ToProductModule(ProductDocument productDocument)
{
return new ProductModule(
Id: Ulid.Parse(product.Id),
Name: product.Name,
Category: product.Category,
Description: product.Description,
ImageFile: product.ImageFile,
Price: product.Price);
Id: Ulid.Parse(productDocument.Id),
Name: productDocument.Name,
Category: productDocument.Category,
Description: productDocument.Description,
ImageFile: productDocument.ImageFile,
Price: productDocument.Price);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,12 @@ public async Task UpdateProduct_ProductId_NotFound_Returns_NotFound(UpdateProduc
var result = await _client
.SetFakeBearerToken(FakePermission.GetPermissions([Policies.CatalogProductUpdatePermission]))
.PutAsJsonAsync("api/v1/catalog/products", updateProductRequest);
var response = await result.Content.ReadFromJsonAsync<ProblemDetails>();
var response = await result.Content.ReadFromJsonAsync<string>();

// Assert
result.StatusCode.ShouldBe(HttpStatusCode.NotFound);
response.ShouldNotBeNull();
response.Detail.ShouldNotBeNull();
response.Detail.ShouldContain($"Entity \"Product\" ({updateProductRequest.Id}) was not found.");
response.ShouldBe(updateProductRequest.Id);
}

[Theory, CatalogRequestAutoData]
Expand Down Expand Up @@ -206,7 +205,7 @@ await _dataSeeder.SeedDataBaseAsync(x =>
result.StatusCode.ShouldBe(HttpStatusCode.NoContent);

await using var session = apiSpecification.GetDocumentStore().LightweightSession();
var valueInDb = session.Query<Product>().FirstOrDefault(x => x.Id == updateProductRequest.Id);
var valueInDb = session.Query<ProductDocument>().FirstOrDefault(x => x.Id == updateProductRequest.Id);

valueInDb!.Name.ShouldBe(updateProductRequest.Name);
valueInDb.Description.ShouldBe(updateProductRequest.Description);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task Populate(IDocumentStore store, CancellationToken cancellation)
await policy.ExecuteAsync(async () =>
{
await using var session = store.LightweightSession();
if (await session.Query<Product>().AnyAsync(cancellation))
if (await session.Query<ProductDocument>().AnyAsync(cancellation))
return;

session.Store(GetPreConfiguredProducts());
Expand All @@ -41,7 +41,7 @@ await policy.ExecuteAsync(async () =>
}
}

private static IEnumerable<Product> GetPreConfiguredProducts()
private static IEnumerable<ProductDocument> GetPreConfiguredProducts()
{
return
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal class CreateProductCommandHandler(IDocumentSession session)
{
public async Task<CreateProductResult> Handle(CreateProductCommand command, CancellationToken cancellationToken)
{
var product = new Product
var product = new ProductDocument
{
Id = command.Id,
Name = command.Name,
Expand All @@ -33,10 +33,10 @@ public async Task<CreateProductResult> Handle(CreateProductCommand command, Canc
return result;
}

private static CreateProductResult MapResult(Product product)
private static CreateProductResult MapResult(ProductDocument productDocument)
{
return new CreateProductResult(
Id: Ulid.Parse(product.Id)
Id: Ulid.Parse(productDocument.Id)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class DeleteProductCommandHandler(
{
public async Task<DeleteProductResult> Handle(DeleteProductCommand command, CancellationToken cancellationToken)
{
session.Delete<Product>(Ulid.Parse(command.Id!).ToString());
session.Delete<ProductDocument>(Ulid.Parse(command.Id!).ToString());
await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

return new DeleteProductResult(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,32 @@ public static IEndpointRouteBuilder MapGetProductByIdEndpoint(this IEndpointRout
return app;
}

private static async Task<Ok<GetProductByIdResponse>> GetProductById(
private static async Task<IResult> GetProductById(
HttpContext context,
string id,
ISender sender)
{
var queryResult = await sender.Send(new GetProductByIdQuery(id)).ConfigureAwait(false);
context.Response.Headers.ETag = $"W/\"{queryResult.Product.Version}\"";
var result = ToResponse(queryResult);
return TypedResults.Ok(result);

switch (queryResult)
{
case Result.NotFound notFound:
return Results.NotFound(notFound.Id);
case Result.Success success:
var etag = $"W/\"{success.Product.Version}\"";
if (context.Request.Headers.IfNoneMatch == etag)
{
return Results.StatusCode(StatusCodes.Status304NotModified);
}
context.Response.Headers.ETag = etag;
var result = ToResponse(success);
return Results.Ok(result);
default:
return Results.InternalServerError();
}
}

private static GetProductByIdResponse? ToResponse(GetProductByIdResult? result)
private static GetProductByIdResponse? ToResponse(Result.Success? result)
{
return result is null
? null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
namespace Catalog.API.Features.Products.GetProductById;

public record GetProductByIdQuery(string Id) : IQuery<GetProductByIdResult>;
public record GetProductByIdQuery(string Id) : IQuery<Result>;

public record GetProductByIdResult(GetProductById Product);
public abstract record Result
{
public record Success(GetProductById Product) : Result;
public record NotFound(string Id) : Result;
}

internal class GetProductByIQueryHandler(
IDocumentSession session) : IQueryHandler<GetProductByIdQuery, GetProductByIdResult>
IDocumentSession session) : IQueryHandler<GetProductByIdQuery, Result>
{
public async Task<GetProductByIdResult> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)
public async Task<Result> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)
{
var product = await session.LoadAsync<Product>(query.Id, cancellationToken).ConfigureAwait(false);
var product = await session.LoadAsync<ProductDocument>(query.Id, cancellationToken).ConfigureAwait(false);

if (product is null)
throw new ProductNotFoundException(Ulid.Parse(query.Id));
return new Result.NotFound(query.Id);

return MapResult(product);
}

private static GetProductByIdResult MapResult(Product product)
private static Result.Success MapResult(ProductDocument productDocument)
{
return new GetProductByIdResult(
return new Result.Success(
Product: new GetProductById(
Id: Ulid.Parse(product.Id),
Name: product.Name,
Category: product.Category,
Description: product.Description,
ImageFile: product.ImageFile,
Price: product.Price,
product.Version));
Id: Ulid.Parse(productDocument.Id),
Name: productDocument.Name,
Category: productDocument.Category,
Description: productDocument.Description,
ImageFile: productDocument.ImageFile,
Price: productDocument.Price,
productDocument.Version));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
{
var pageSize = query.PaginationRequest.PageSize;
var pageIndex = query.PaginationRequest.PageIndex;
var productsAsQueryable = session.Query<Product>().AsQueryable();
var productsAsQueryable = session.Query<ProductDocument>().AsQueryable();
var totalItems = await productsAsQueryable.CountAsync(cancellationToken).ConfigureAwait(false);
var products = await productsAsQueryable.Skip(pageSize * pageIndex)
.Take(pageSize)
Expand All @@ -24,10 +24,10 @@

var result = MapToResult(products);

return new GetProductsResult(new PaginatedItems<ProductModule>(pageIndex, pageSize, totalItems, result));

Check warning on line 27 in src/Services/Catalog/Catalog.API/Features/Products/GetProducts/GetProductsHandler.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'data' in 'PaginatedItems<ProductModule>.PaginatedItems(int pageIndex, int pageSize, long count, IEnumerable<ProductModule> data)'.

Check warning on line 27 in src/Services/Catalog/Catalog.API/Features/Products/GetProducts/GetProductsHandler.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'data' in 'PaginatedItems<ProductModule>.PaginatedItems(int pageIndex, int pageSize, long count, IEnumerable<ProductModule> data)'.
}

private static ReadOnlyCollection<ProductModule>? MapToResult(IReadOnlyCollection<Product>? products)
private static ReadOnlyCollection<ProductModule>? MapToResult(IReadOnlyCollection<ProductDocument>? products)
{
return products?.Select(x => new ProductModule(
Id: Ulid.Parse(x.Id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
var pageSize = query.PaginationRequest.PageSize;
var pageIndex = query.PaginationRequest.PageIndex;
var productAsQueryable =
session.Query<Product>().Where(p => p.Category.Contains(query.Category!)).AsQueryable();
session.Query<ProductDocument>().Where(p => p.Category.Contains(query.Category!)).AsQueryable();
var totalItems = await productAsQueryable.CountAsync(cancellationToken).ConfigureAwait(false);
var product = await productAsQueryable
.Skip(pageSize * pageIndex)
Expand All @@ -29,10 +29,10 @@
var result = MapToResult(product);

return new GetProductByCategoryResult(
new PaginatedItems<ProductModule>(pageIndex, pageSize, totalItems, result));

Check warning on line 32 in src/Services/Catalog/Catalog.API/Features/Products/GetProductsByCategory/GetProductsByCategoryHandler.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'data' in 'PaginatedItems<ProductModule>.PaginatedItems(int pageIndex, int pageSize, long count, IEnumerable<ProductModule> data)'.

Check warning on line 32 in src/Services/Catalog/Catalog.API/Features/Products/GetProductsByCategory/GetProductsByCategoryHandler.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'data' in 'PaginatedItems<ProductModule>.PaginatedItems(int pageIndex, int pageSize, long count, IEnumerable<ProductModule> data)'.
}

private static ReadOnlyCollection<ProductModule>? MapToResult(IReadOnlyCollection<Product>? products)
private static ReadOnlyCollection<ProductModule>? MapToResult(IReadOnlyCollection<ProductDocument>? products)
{
return products?.Select(x => new ProductModule(
Id: Ulid.Parse(x.Id),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Catalog.API.Authorization;
using System.Net;
using Catalog.API.Authorization;
using OpenTelemetry.Trace;

namespace Catalog.API.Features.Products.UpdateProduct;

Expand All @@ -22,14 +24,22 @@ private static async Task<IResult> UpdateProduct(
UpdateProductRequest request,
ISender sender)
{
var eTag = context.Request.Headers["If-Match"];
var eTag = context.Request.Headers.IfMatch;
var command = request.ToCommand(eTag);

if (command is null)
return TypedResults.BadRequest("Request is null");
return Results.BadRequest("Request is null");

await sender.Send(command).ConfigureAwait(false);
return TypedResults.NoContent();
var result = await sender.Send(command).ConfigureAwait(false);

return result switch
{
Result.NotFound notFound => Results.NotFound(notFound.Id),
Result.InvalidEtag invalidEtag => Results.Problem($"Invalid Etag {invalidEtag}",
statusCode: StatusCodes.Status412PreconditionFailed),
Result.Success => Results.NoContent(),
_ => Results.InternalServerError()
};
}

private static UpdateProductCommand? ToCommand(this UpdateProductRequest? request, string? etag)
Expand Down
Loading
Loading