Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/DotNetBoilerplate.Api/Books/BrowseBooksEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static void Map(IEndpointRouteBuilder app)
.WithSummary("Browse books with optional BookStoreId parameter");
}

private static async Task<Results<Ok<IEnumerable<BookDto>>, Ok<BookDto>, NotFound>> Handle(
private static async Task<Results<Ok<IEnumerable<BookDto>>, NotFound>> Handle(
[FromQuery] Guid? bookStoreId,
[FromServices] IQueryDispatcher queryDispatcher,
CancellationToken ct
Expand Down
37 changes: 37 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/AddBookToCatalogEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using DotNetBoilerplate.Application.Catalogs.AddBook;
using DotNetBoilerplate.Shared.Abstractions.Commands;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

public class AddBookToCatalogEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapPost("{catalogId:guid}/books", Handle)
.RequireAuthorization()
.WithSummary("Add book to catalog");
}

private static async Task<Ok<Response>> Handle(
[FromRoute] Guid catalogId,
[FromBody] Request request,
[FromServices] ICommandDispatcher commandDispatcher,
CancellationToken ct
)
{
var command = new AddBookToCatalogCommand(catalogId, request.BookId);
var result = await commandDispatcher.DispatchAsync<AddBookToCatalogCommand, Guid>(command, ct);

return TypedResults.Ok(new Response(result));
}
internal sealed record Response(
Guid Id
);

private class Request
{
public Guid BookId { get; init; }
}
}
30 changes: 30 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/BrowseBooksInCatalogEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using DotNetBoilerplate.Application.Catalogs.BrowseBooks;
using DotNetBoilerplate.Application.Books.DTO;
using DotNetBoilerplate.Shared.Abstractions.Queries;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

public class BrowseBooksInCatalogEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapGet("{catalogId:guid}/books", Handle)
.WithSummary("Browse books with CatalogId parameter");
}

private static async Task<Results<Ok<IEnumerable<BookDto>>, NotFound>> Handle(
[FromRoute] Guid catalogId,
[FromServices] IQueryDispatcher queryDispatcher,
CancellationToken ct
)
{
var query = new BrowseBooksInCatalogQuery(catalogId);
var result = await queryDispatcher.QueryAsync(query, ct);

return result is null
? TypedResults.NotFound()
: TypedResults.Ok(result);
}
}
30 changes: 30 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/BrowseCatalogsEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using DotNetBoilerplate.Application.Catalogs.Browse;
using DotNetBoilerplate.Application.Catalogs.DTO;
using DotNetBoilerplate.Shared.Abstractions.Queries;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

public class BrowseCatalogsEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapGet("", Handle)
.WithSummary("Browse catalogs with optional BookStoreId parameter");
}

private static async Task<Results<Ok<IEnumerable<CatalogDto>>, NotFound>> Handle(
[FromQuery] Guid? bookStoreId,
[FromServices] IQueryDispatcher queryDispatcher,
CancellationToken ct
)
{
var query = new BrowseCatalogsQuery(bookStoreId);
var result = await queryDispatcher.QueryAsync(query, ct);

return result is null
? TypedResults.NotFound()
: TypedResults.Ok(result);
}
}
26 changes: 26 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/CatalogsEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using DotNetBoilerplate.Api.Catalogs;

namespace DotNetBoilerplate.Api.Catalogs;

internal static class CatalogsEndpoints
{
public const string BasePath = "catalogs";
public const string Tags = "Catalogs";

public static void MapCatalogsEndpoints(this WebApplication app)
{
var group = app.MapGroup(BasePath)
.WithTags(Tags);

group
.MapEndpoint<CreateCatalogEndpoint>()
.MapEndpoint<UpdateCatalogEndpoint>()
.MapEndpoint<BrowseCatalogsEndpoint>()
.MapEndpoint<GetCatalogByIdEndpoint>()
.MapEndpoint<DeleteCatalogEndpoint>()
.MapEndpoint<AddBookToCatalogEndpoint>()
.MapEndpoint<BrowseBooksInCatalogEndpoint>()
.MapEndpoint<RemoveBookFromCatalogEndpoint>();
}
}

46 changes: 46 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/CreateCatalogEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using DotNetBoilerplate.Application.Catalogs.Create;
using DotNetBoilerplate.Shared.Abstractions.Commands;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

internal sealed class CreateCatalogEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapPost("", Handle)
.RequireAuthorization()
.WithSummary("Create catalog");
}
private static async Task<Ok<Response>> Handle(
[FromBody] Request request,
[FromServices] ICommandDispatcher commandDispatcher,
CancellationToken ct
)
{
var command = new CreateCatalogCommand(
request.Name,
request.Genre,
request.Description
);

var result = await commandDispatcher.DispatchAsync<CreateCatalogCommand, Guid>(command, ct);

return TypedResults.Ok(new Response(result));
}

internal sealed record Response(
Guid Id
);

private sealed class Request
{
[Required] public string Name { get; init; }

[Required] public string Genre { get; init; }

[Required] public string Description { get; init; }
}
}
29 changes: 29 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/DeleteCatalogEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using DotNetBoilerplate.Application.Catalogs.Delete;
using DotNetBoilerplate.Shared.Abstractions.Commands;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

internal sealed class DeleteCatalogEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapDelete("{id:guid}", Handle)
.RequireAuthorization()
.WithSummary("Delete catalog by Id");
}

private static async Task<IResult> Handle(
Guid id,
[FromServices] ICommandDispatcher commandDispatcher,
CancellationToken ct
)
{
var command = new DeleteCatalogCommand(id);

await commandDispatcher.DispatchAsync<DeleteCatalogCommand>(command, ct);

return TypedResults.NoContent();
}
}
31 changes: 31 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/GetCatalogByIdEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using DotNetBoilerplate.Application.Catalogs.Get;
using DotNetBoilerplate.Application.Catalogs.DTO;
using DotNetBoilerplate.Shared.Abstractions.Queries;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

public class GetCatalogByIdEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapGet("{id:guid}", Handle)
.WithSummary("Browse catalog by Id");
}

private static async Task<Results<Ok<CatalogDto>, NotFound>> Handle(
[FromRoute] Guid id,
[FromServices] IQueryDispatcher queryDispatcher,
CancellationToken ct
)
{
var query = new GetCatalogByIdQuery(id);

var result = await queryDispatcher.QueryAsync(query, ct);

if (result is null) return TypedResults.NotFound();

return TypedResults.Ok(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
using DotNetBoilerplate.Application.Catalogs.Delete;
using DotNetBoilerplate.Application.Catalogs.RemoveBook;
using DotNetBoilerplate.Shared.Abstractions.Commands;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

internal sealed class RemoveBookFromCatalogEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapDelete("{catalogId:guid}/books/{bookId:guid}", Handle)
.RequireAuthorization()
.WithSummary("Remove book from catalog by Id");
}

private static async Task<IResult> Handle(
Guid catalogId, Guid bookId,
[FromServices] ICommandDispatcher commandDispatcher,
CancellationToken ct
)
{
var command = new RemoveBookFromCatalogCommand(catalogId, bookId);

await commandDispatcher.DispatchAsync<RemoveBookFromCatalogCommand>(command, ct);

return TypedResults.NoContent();
}
private sealed class Request
{
[Required] public Guid BookId { get; init; }
}
}
52 changes: 52 additions & 0 deletions src/DotNetBoilerplate.Api/Catalogs/UpdateCatalogEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.ComponentModel.DataAnnotations;
using DotNetBoilerplate.Application.Catalogs.Update;
using DotNetBoilerplate.Shared.Abstractions.Commands;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace DotNetBoilerplate.Api.Catalogs;

public class UpdateCatalogEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapPut("{id:guid}", Handle)
.RequireAuthorization()
.WithSummary("Update a catalog");
}

private static async Task<Ok<Response>> Handle(
[FromRoute] Guid id,
[FromBody] Request request,
[FromServices] ICommandDispatcher commandDispatcher,
CancellationToken ct
)
{
var command = new UpdateCatalogCommand(
id,
request.Name,
request.Genre,
request.Description
);

var result = await commandDispatcher.DispatchAsync<UpdateCatalogCommand, Guid>(command, ct);

return TypedResults.Ok(new Response(result));
}

internal sealed record Response(
Guid Id
);

private sealed class Request
{
// ReSharper disable once UnusedAutoPropertyAccessor.Local
[Required] public string Name { get; init; }

// ReSharper disable once UnusedAutoPropertyAccessor.Local
[Required] public string Genre { get; init; }

// ReSharper disable once UnusedAutoPropertyAccessor.Local
[Required] public string Description { get; init; }
}
}
2 changes: 2 additions & 0 deletions src/DotNetBoilerplate.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DotNetBoilerplate.Api.Books;
using DotNetBoilerplate.Api.Reviews;
using DotNetBoilerplate.Api.Users;
using DotNetBoilerplate.Api.Catalogs;
using DotNetBoilerplate.Application;
using DotNetBoilerplate.Core;
using DotNetBoilerplate.Infrastructure;
Expand All @@ -24,6 +25,7 @@
app.MapBookStoresEndpoints();
app.MapBookEndpoints();
app.MapReviewEndpoints();
app.MapCatalogsEndpoints();

app.UseInfrastructure();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using DotNetBoilerplate.Shared.Abstractions.Commands;

namespace DotNetBoilerplate.Application.Catalogs.AddBook;

public sealed record AddBookToCatalogCommand(Guid CatalogId, Guid BookId) : ICommand<Guid>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using DotNetBoilerplate.Core.Catalogs;
using DotNetBoilerplate.Core.Books;
using DotNetBoilerplate.Shared.Abstractions.Commands;
using DotNetBoilerplate.Application.Exceptions;

namespace DotNetBoilerplate.Application.Catalogs.AddBook;

internal sealed class AddBookToCatalogHandler(
ICatalogRepository catalogRepository,
IBookRepository bookRepository
) : ICommandHandler<AddBookToCatalogCommand, Guid>
{
public async Task<Guid> HandleAsync(AddBookToCatalogCommand command)
{
var catalog = await catalogRepository.GetByIdAsync(command.CatalogId);
if (catalog is null)
throw new CatalogNotFoundException();
var book = await bookRepository.GetByIdAsync(command.BookId);
if (book is null)
throw new BookNotFoundException();

await catalogRepository.AddBookToCatalogAsync(book, catalog);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

albo catalog.AddBook(book) albo book.SetCatalog(catalog)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nie powinniśmy przekładać odpowiedzialności za procesy biznesowe na repozytorium

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do przemyślenia czy czasem nie zwrobić tak że Book ma public Catalog, a katalog nie ma List - bo za każdym update byśmy musieli to zaciągać z bazy


return catalog.Id;
}
}
Loading