diff --git a/exercise.webapi/DTO/AuthorDTO.cs b/exercise.webapi/DTO/AuthorDTO.cs new file mode 100644 index 0000000..c25a923 --- /dev/null +++ b/exercise.webapi/DTO/AuthorDTO.cs @@ -0,0 +1,5 @@ +namespace exercise.webapi.DTO +{ + public record AuthorGet(int Id, string FirstName, string LastName, string Email, IEnumerable Books); + public record AuthorInternal(int id, string FirstName, string LastName, string Email); +} diff --git a/exercise.webapi/DTO/BookDTO.cs b/exercise.webapi/DTO/BookDTO.cs new file mode 100644 index 0000000..e0172ef --- /dev/null +++ b/exercise.webapi/DTO/BookDTO.cs @@ -0,0 +1,7 @@ +namespace exercise.webapi.DTO +{ + public record BookGet(int Id, string Title, AuthorInternal author); + public record BookPost(string Title, int AuthorId); + public record BookPut(int AuthorId); + public record BookInternal(int id, string Title); +} diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs new file mode 100644 index 0000000..d7680b2 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,75 @@ +using exercise.webapi.DTO; +using exercise.webapi.Repository; +using Microsoft.AspNetCore.Mvc; + +namespace exercise.webapi.Endpoints +{ + public static class AuthorApi + { + public static void ConfigureAuthorsApi(this WebApplication app) + { + var authors = app.MapGroup("authors"); + + authors.MapGet("/", GetAuthors); + authors.MapGet("/{id}", GetAuthor); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + private static async Task GetAuthors(IAuthorRepository authorRepository) + { + try + { + var authors = await authorRepository.GetAllAuthors(); + + var atrs = authors.Select(author => + { + return new AuthorGet( + author.Id, + author.FirstName, + author.LastName, + author.Email, + author.Books.Select(b => new BookInternal( + b.Id, + b.Title + ) + )); + }); + + return TypedResults.Ok(atrs); + } + catch(Exception ex) + { + return TypedResults.Problem(ex.Message); + } + } + + [ProducesResponseType(StatusCodes.Status200OK)] + private static async Task GetAuthor(IAuthorRepository authorRepository, int id) + { + try + { + var author = await authorRepository.GetAuthor(id); + + if (author == null) + return TypedResults.Problem(null); + + AuthorGet atr = new AuthorGet( + author.Id, + author.FirstName, + author.LastName, + author.Email, + author.Books.Select(b => new BookInternal( + b.Id, + b.Title + )) + ); + + return TypedResults.Ok(atr); + } + catch (Exception ex) + { + return TypedResults.Problem(ex.Message); + } + } + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..78cfce9 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,5 +1,11 @@ -using exercise.webapi.Models; +using System; +using System.Security.Principal; +using exercise.webapi.DTO; +using exercise.webapi.Models; using exercise.webapi.Repository; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.TagHelpers; using static System.Reflection.Metadata.BlobBuilder; namespace exercise.webapi.Endpoints @@ -8,13 +14,119 @@ public static class BookApi { public static void ConfigureBooksApi(this WebApplication app) { - app.MapGet("/books", GetBooks); + var books = app.MapGroup("books"); + + books.MapGet("/", GetBooks); + books.MapGet("/{id}", GetBook); + books.MapPut("/{id}", UpdateBook); + books.MapDelete("/{id}", DeleteBook); + books.MapPost("/", AddBook); } + [ProducesResponseType(StatusCodes.Status200OK)] private static async Task GetBooks(IBookRepository bookRepository) { - var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + try + { + var books = await bookRepository.GetAllBooks(); + + var bks = books.Select(book => + { + return new BookGet( + book.Id, + book.Title, + new AuthorInternal( + book.Author.Id, + book.Author.FirstName, + book.Author.LastName, + book.Author.Email + ) + ); + }); + + return TypedResults.Ok(bks); + } + catch (Exception ex) + { + return TypedResults.Problem(ex.Message); + } + } + + [ProducesResponseType(StatusCodes.Status200OK)] + private static async Task GetBook(IBookRepository bookRepository, int id) + { + try + { + var book = await bookRepository.GetBook(id); + + BookGet bk = new BookGet( + book.Id, + book.Title, + new AuthorInternal( + book.Author.Id, + book.Author.FirstName, + book.Author.LastName, + book.Author.Email + ) + ); + + return TypedResults.Ok(bk); + } + catch (Exception ex) + { + return TypedResults.Problem(ex.Message); + } + } + + [ProducesResponseType(StatusCodes.Status200OK)] + private static async Task UpdateBook(IBookRepository bookRepository, int id, int newAuthorId) + { + try + { + return TypedResults.Ok(await bookRepository.UpdateBook(id, newAuthorId)); + } + catch (Exception ex) + { + return TypedResults.Problem(ex.Message); + } + } + + [ProducesResponseType(StatusCodes.Status200OK)] + private static async Task DeleteBook(IBookRepository bookRepository, int id) + { + try + { + return TypedResults.Ok(await bookRepository.DeleteBook(id)); + } + catch (Exception ex) + { + return TypedResults.Problem(ex.Message); + } + } + + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task AddBook(IBookRepository bookRepository, IAuthorRepository authorRepository, BookPost book) + { + try + { + if (await authorRepository.GetAuthor(book.AuthorId) == null) + return TypedResults.NotFound("Author not found."); + + Book bk = new Book() + { + Title = book.Title, + AuthorId = book.AuthorId + }; + await bookRepository.AddBook(bk); + + return TypedResults.Created($"https://localhost:7054/books/{bk.Id}", bk); + } + catch (Exception ex) + { + return TypedResults.BadRequest(ex.Message); + } } } } diff --git a/exercise.webapi/Models/Author.cs b/exercise.webapi/Models/Author.cs index 9f47878..538988a 100644 --- a/exercise.webapi/Models/Author.cs +++ b/exercise.webapi/Models/Author.cs @@ -9,7 +9,7 @@ public class Author public string LastName { get; set; } public string Email { get; set; } - [JsonIgnore] // Todo: replace this with DTO approach + public ICollection Books { get; set; } = new List(); } } diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 43dec56..c0ed639 100644 --- a/exercise.webapi/Program.cs +++ b/exercise.webapi/Program.cs @@ -11,6 +11,7 @@ builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Library")); builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); @@ -29,4 +30,5 @@ app.UseHttpsRedirection(); app.ConfigureBooksApi(); +app.ConfigureAuthorsApi(); app.Run(); diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..67c4845 --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,27 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.webapi.Repository +{ + public class AuthorRepository : IAuthorRepository + { + DataContext _db; + + public AuthorRepository(DataContext db) + { + _db = db; + } + + public async Task> GetAllAuthors() + { + return await _db.Authors.Include(a => a.Books).ToListAsync(); + + } + + public async Task GetAuthor(int id) + { + return await _db.Authors.Include(a => a.Books).FirstOrDefaultAsync(a => a.Id == id); + } + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..9b5663a 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -18,5 +18,33 @@ public async Task> GetAllBooks() return await _db.Books.Include(b => b.Author).ToListAsync(); } + + public async Task GetBook(int id) + { + return await _db.Books.Include(b => b.Author).FirstOrDefaultAsync(b => b.Id == id); + } + + public async Task UpdateBook(int id, int newAuthorId) + { + var book = await _db.Books.FirstOrDefaultAsync(b => b.Id == id); + book.AuthorId = newAuthorId; + await _db.SaveChangesAsync(); + return book; + } + + public async Task DeleteBook(int id) + { + var book = await _db.Books.FirstOrDefaultAsync(b => b.Id == id); + _db.Books.Remove(book); + await _db.SaveChangesAsync(); + return true; + } + + public async Task AddBook(Book book) + { + await _db.Books.AddAsync(book); + await _db.SaveChangesAsync(); + return book; + } } } diff --git a/exercise.webapi/Repository/IAuthorRepository.cs b/exercise.webapi/Repository/IAuthorRepository.cs new file mode 100644 index 0000000..64fea74 --- /dev/null +++ b/exercise.webapi/Repository/IAuthorRepository.cs @@ -0,0 +1,10 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public interface IAuthorRepository + { + public Task> GetAllAuthors(); + public Task GetAuthor(int id); + } +} diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index f860016..06deef9 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -5,5 +5,9 @@ namespace exercise.webapi.Repository public interface IBookRepository { public Task> GetAllBooks(); + public Task GetBook(int id); + public Task UpdateBook(int id, int newAuthorId); + public Task DeleteBook(int id); + public Task AddBook(Book book); } } diff --git a/exercise.webapi/exercise.webapi.csproj b/exercise.webapi/exercise.webapi.csproj index 0ff4269..dca11e6 100644 --- a/exercise.webapi/exercise.webapi.csproj +++ b/exercise.webapi/exercise.webapi.csproj @@ -8,10 +8,20 @@ - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - +