From 426201d51b946768d38e8c5426b34155f4f62679 Mon Sep 17 00:00:00 2001 From: Tonnes Date: Wed, 5 Feb 2025 14:51:20 +0100 Subject: [PATCH] Core complete --- exercise.webapi/DTO/AuthorDTO.cs | 12 ++ exercise.webapi/DTO/AuthorPost.cs | 10 ++ exercise.webapi/DTO/BookDTO.cs | 12 ++ exercise.webapi/DTO/BookPost.cs | 10 ++ exercise.webapi/DTO/BookPut.cs | 10 ++ exercise.webapi/Endpoints/AuthorApi.cs | 54 ++++++++ exercise.webapi/Endpoints/BookApi.cs | 124 +++++++++++++++++- exercise.webapi/Models/Author.cs | 2 - exercise.webapi/Models/Book.cs | 1 - exercise.webapi/Program.cs | 2 + .../Repository/AuthorRepository.cs | 28 ++++ exercise.webapi/Repository/BookRepository.cs | 28 ++++ .../Repository/IAuthorRepository.cs | 10 ++ exercise.webapi/Repository/IBookRepository.cs | 6 + exercise.webapi/exercise.webapi.csproj | 11 +- exercise.webapi/exercise.webapi.http | 48 ++++++- 16 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 exercise.webapi/DTO/AuthorDTO.cs create mode 100644 exercise.webapi/DTO/AuthorPost.cs create mode 100644 exercise.webapi/DTO/BookDTO.cs create mode 100644 exercise.webapi/DTO/BookPost.cs create mode 100644 exercise.webapi/DTO/BookPut.cs create mode 100644 exercise.webapi/Endpoints/AuthorApi.cs create mode 100644 exercise.webapi/Repository/AuthorRepository.cs create mode 100644 exercise.webapi/Repository/IAuthorRepository.cs diff --git a/exercise.webapi/DTO/AuthorDTO.cs b/exercise.webapi/DTO/AuthorDTO.cs new file mode 100644 index 0000000..fc4f028 --- /dev/null +++ b/exercise.webapi/DTO/AuthorDTO.cs @@ -0,0 +1,12 @@ +namespace exercise.webapi.DTO +{ + public class AuthorDTO + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + + public List Books { get; set; } + } +} diff --git a/exercise.webapi/DTO/AuthorPost.cs b/exercise.webapi/DTO/AuthorPost.cs new file mode 100644 index 0000000..556de6f --- /dev/null +++ b/exercise.webapi/DTO/AuthorPost.cs @@ -0,0 +1,10 @@ +namespace exercise.webapi.DTO +{ + public class AuthorPost + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + } +} diff --git a/exercise.webapi/DTO/BookDTO.cs b/exercise.webapi/DTO/BookDTO.cs new file mode 100644 index 0000000..4ef266f --- /dev/null +++ b/exercise.webapi/DTO/BookDTO.cs @@ -0,0 +1,12 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookDTO + { + public int Id { get; set; } + public string Title { get; set; } + public int AuthorId { get; set; } + public AuthorDTO Author { get; set; } + } +} diff --git a/exercise.webapi/DTO/BookPost.cs b/exercise.webapi/DTO/BookPost.cs new file mode 100644 index 0000000..7110854 --- /dev/null +++ b/exercise.webapi/DTO/BookPost.cs @@ -0,0 +1,10 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookPost + { + public string Title { get; set; } + public int AuthorId { get; set; } + } +} diff --git a/exercise.webapi/DTO/BookPut.cs b/exercise.webapi/DTO/BookPut.cs new file mode 100644 index 0000000..1f6f81d --- /dev/null +++ b/exercise.webapi/DTO/BookPut.cs @@ -0,0 +1,10 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookPut + { + public string? Title { get; set; } + public int? AuthorId { get; set; } + } +} diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs new file mode 100644 index 0000000..ec4edb2 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,54 @@ +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Repository; +using static System.Reflection.Metadata.BlobBuilder; + +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); + } + + private static async Task GetAuthors(IAuthorRepository repository) + { + var authors = await repository.GetAllAuthors(); + List result = authors.Select(a => new AuthorDTO + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + Email = a.Email, + Books = a.Books.Select(b => new BookDTO + { + Id = b.Id, + Title = b.Title, + }).ToList() + }).ToList(); + return TypedResults.Ok(result); + } + + private static async Task GetAuthor(IAuthorRepository repository, int id) + { + var author = await repository.GetAuthor(id); + if (author == null) return TypedResults.NotFound("Not Found"); + AuthorDTO result = new AuthorDTO + { + Id = author.Id, + FirstName = author.FirstName, + LastName = author.LastName, + Email = author.Email, + Books = author.Books.Select(b => new BookDTO + { + Id = b.Id, + Title = b.Title, + }).ToList() + }; + return TypedResults.Ok(result); + } + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..69887b0 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,4 +1,5 @@ -using exercise.webapi.Models; +using exercise.webapi.DTO; +using exercise.webapi.Models; using exercise.webapi.Repository; using static System.Reflection.Metadata.BlobBuilder; @@ -8,13 +9,126 @@ 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.MapPost("/", AddBook); + books.MapPut("/{id}", UpdateBook); + books.MapDelete("/{id}", DeleteBook); } - private static async Task GetBooks(IBookRepository bookRepository) + private static async Task GetBooks(IBookRepository repository) { - var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + var books = await repository.GetAllBooks(); + List result = books.Select(b => new BookDTO + { + Id = b.Id, + Title = b.Title, + AuthorId = b.AuthorId, + Author = new AuthorDTO + { + Id = b.Author.Id, + FirstName = b.Author.FirstName, + LastName = b.Author.LastName, + Email = b.Author.Email + }}).ToList(); + return TypedResults.Ok(result); } + + private static async Task GetBook(IBookRepository repository, int id) + { + var book = await repository.GetBook(id); + if (book == null) return TypedResults.NotFound("Not Found"); + BookDTO result = new BookDTO + { + Id = book.Id, + Title = book.Title, + AuthorId = book.AuthorId, + Author = new AuthorDTO + { + Id = book.Author.Id, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + } + }; + return TypedResults.Ok(result); + } + + private static async Task AddBook(IBookRepository bookRepository, IAuthorRepository authorRepository, BookPost model) + { + if ( model.Title == null || model.AuthorId == null) return TypedResults.BadRequest(); + Author author = await authorRepository.GetAuthor(model.AuthorId); + if (author == null) return TypedResults.NotFound("Author not found"); + + Book book = new Book + { + Title = model.Title, + AuthorId = model.AuthorId, + }; + await bookRepository.AddBook(book); + + BookDTO result = new BookDTO + { + Id = book.Id, + Title = book.Title, + AuthorId = book.AuthorId, + Author = new AuthorDTO + { + Id = book.Author.Id, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + } + }; + return TypedResults.Ok(result); + } + + private static async Task UpdateBook(IBookRepository bookRepository, IAuthorRepository authorRepository, int id, BookPut model) + { + if (model.Title == null && model.AuthorId == null) return TypedResults.BadRequest(); + Author author = await authorRepository.GetAuthor((int)model.AuthorId); + if (author == null) return TypedResults.NotFound("Author not found"); + Book book = await bookRepository.GetBook(id); + + book.AuthorId = (int)author.Id; + await bookRepository.UpdateBook(book); + + BookDTO result = new BookDTO + { + Id = book.Id, + Title = book.Title, + Author = new AuthorDTO + { + Id = book.Author.Id, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + } + }; + return TypedResults.Ok(result); + } + + private static async Task DeleteBook(IBookRepository repository, int id) + { + var book = await repository.GetBook(id); + if (book == null) return TypedResults.NotFound("Not Found"); + if (!await repository.DeleteBook(id)) return TypedResults.NotFound("Not Found"); + BookDTO result = new BookDTO + { + Id = book.Id, + Title = book.Title, + AuthorId = book.AuthorId, + Author = new AuthorDTO + { + Id = book.Author.Id, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + } + }; + return TypedResults.Ok(result); + } + } } diff --git a/exercise.webapi/Models/Author.cs b/exercise.webapi/Models/Author.cs index 9f47878..6da6f42 100644 --- a/exercise.webapi/Models/Author.cs +++ b/exercise.webapi/Models/Author.cs @@ -8,8 +8,6 @@ public class Author public string FirstName { get; set; } 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/Models/Book.cs b/exercise.webapi/Models/Book.cs index 9534929..f898b7d 100644 --- a/exercise.webapi/Models/Book.cs +++ b/exercise.webapi/Models/Book.cs @@ -6,7 +6,6 @@ public class Book { public int Id { get; set; } public string Title { get; set; } - public int AuthorId { get; set; } public Author Author { get; set; } } 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..f4e2a35 --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,28 @@ +using exercise.webapi.Data; +using exercise.webapi.DTO; +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..4238a44 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -1,4 +1,5 @@ using exercise.webapi.Data; +using exercise.webapi.DTO; using exercise.webapi.Models; using Microsoft.EntityFrameworkCore; @@ -16,7 +17,34 @@ public BookRepository(DataContext db) 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 AddBook(Book book) + { + _db.Books.Add(book); + await _db.SaveChangesAsync(); + return book; + } + + public async Task UpdateBook(Book book) + { + _db.Books.Update(book); + await _db.SaveChangesAsync(); + return book; + } + + public async Task DeleteBook(int id) + { + var target = await _db.Books.Include(b => b.Author).FirstOrDefaultAsync(b => b.Id == id); + if (target == null) return false; + _db.Books.Remove(target); + await _db.SaveChangesAsync(); + return true; } } } 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..3853827 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -1,9 +1,15 @@ using exercise.webapi.Models; +using exercise.webapi.DTO; namespace exercise.webapi.Repository { public interface IBookRepository { public Task> GetAllBooks(); + public Task GetBook(int id); + public Task AddBook(Book book); + public Task UpdateBook(Book book); + public Task DeleteBook(int id); + } } diff --git a/exercise.webapi/exercise.webapi.csproj b/exercise.webapi/exercise.webapi.csproj index 0ff4269..1381e7c 100644 --- a/exercise.webapi/exercise.webapi.csproj +++ b/exercise.webapi/exercise.webapi.csproj @@ -9,9 +9,14 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/exercise.webapi/exercise.webapi.http b/exercise.webapi/exercise.webapi.http index 2fcaed3..7f855ef 100644 --- a/exercise.webapi/exercise.webapi.http +++ b/exercise.webapi/exercise.webapi.http @@ -1,6 +1,52 @@ @exercise.webapi_HostAddress = http://localhost:5201 -GET {{exercise.webapi_HostAddress}}/weatherforecast/ +GET {{exercise.webapi_HostAddress}}/Authors/ Accept: application/json ### + +GET {{exercise.webapi_HostAddress}}/Books/ +Accept: application/json + +### + +GET {{exercise.webapi_HostAddress}}/Authors/245 +Accept: application/json + +### + +GET {{exercise.webapi_HostAddress}}/Books/250/ +Accept: application/json + +### + + +POST {{exercise.webapi_HostAddress}}/Books/ +Content-Type: application/json +{ + "title": "Me", + "authorId": 24 +} +### + +PUT {{exercise.webapi_HostAddress}}/Books/250 +Content-Type: application/json +{ + "authorId": 245 +} +### + +GET {{exercise.webapi_HostAddress}}/Books/1/ +Accept: application/json + +### + +DELETE {{exercise.webapi_HostAddress}}/Books/4 +Accept: application/json + +### + +GET {{exercise.webapi_HostAddress}}/Books/4/ +Accept: application/json + +### \ No newline at end of file