diff --git a/exercise.webapi/DTO/AuthorDTO.cs b/exercise.webapi/DTO/AuthorDTO.cs new file mode 100644 index 0000000..63415da --- /dev/null +++ b/exercise.webapi/DTO/AuthorDTO.cs @@ -0,0 +1,11 @@ + +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; } + } +} diff --git a/exercise.webapi/DTO/AuthorListDTO.cs b/exercise.webapi/DTO/AuthorListDTO.cs new file mode 100644 index 0000000..24afc73 --- /dev/null +++ b/exercise.webapi/DTO/AuthorListDTO.cs @@ -0,0 +1,12 @@ +namespace exercise.webapi.DTO +{ + public class AuthorListDTO + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public List BookList { get; set; } = new List(); + + } +} diff --git a/exercise.webapi/DTO/BookDTO.cs b/exercise.webapi/DTO/BookDTO.cs new file mode 100644 index 0000000..f91b3cb --- /dev/null +++ b/exercise.webapi/DTO/BookDTO.cs @@ -0,0 +1,13 @@ +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/BookListDTO.cs b/exercise.webapi/DTO/BookListDTO.cs new file mode 100644 index 0000000..507655f --- /dev/null +++ b/exercise.webapi/DTO/BookListDTO.cs @@ -0,0 +1,8 @@ +namespace exercise.webapi.DTO +{ + public class BookListDTO + { + public int Id { get; set; } + public string Title { get; set; } + } +} diff --git a/exercise.webapi/DTO/BookPost.cs b/exercise.webapi/DTO/BookPost.cs new file mode 100644 index 0000000..a93de84 --- /dev/null +++ b/exercise.webapi/DTO/BookPost.cs @@ -0,0 +1,8 @@ +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..9a6ab0b --- /dev/null +++ b/exercise.webapi/DTO/BookPut.cs @@ -0,0 +1,8 @@ +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..6751970 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,66 @@ +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Repository; + +namespace exercise.webapi.Endpoints +{ + public static class AuthorApi + { + public static void ConfigureAuthorApi(this WebApplication app) + { + var authors = app.MapGroup("authors"); + authors.MapGet("/", GetAuthors); + authors.MapGet("/{id}", GetAuthor); + } + + + #region Get authors + private static async Task GetAuthors(IAuthorRepository authorRepository) + { + //Payload> payload = new Payload>(); + + + var results = await authorRepository.GetAllAuthors(); + var resultsDTO = results.Select(a => new AuthorListDTO + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + Email = a.Email, + BookList = a.Books.Select(b => new BookListDTO + { + Id = b.Id, + Title = b.Title, + }).ToList() + }); + + return TypedResults.Ok(resultsDTO); + } + + public static async Task GetAuthor(IAuthorRepository authorRepository, int id) + { + var author = await authorRepository.GetAuthor(id); + if (author == null) + { + return Results.NotFound(); + } + var authorDTO = new AuthorListDTO + { + Id = author.Id, + FirstName = author.FirstName, + LastName = author.LastName, + Email = author.Email, + BookList = author.Books.Select(b => new BookListDTO + { + Id = b.Id, + Title = b.Title, + + }).ToList() + }; + return Results.Ok(authorDTO); + + } + #endregion + + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..ce90ca0 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,4 +1,6 @@ -using exercise.webapi.Models; +using System; +using exercise.webapi.DTO; +using exercise.webapi.Models; using exercise.webapi.Repository; using static System.Reflection.Metadata.BlobBuilder; @@ -8,13 +10,145 @@ 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.MapDelete("/{id}", DeleteBook); + books.MapPut("/{id}", UpdateBook); } + #region Get books private static async Task GetBooks(IBookRepository bookRepository) { - var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + //Payload> payload = new Payload>(); + + + var results = await bookRepository.GetAllBooks(); + var resultsDTO = results.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 + } + }); + + return TypedResults.Ok(resultsDTO); + } + + public static async Task GetBook(IBookRepository bookRepository, int id) + { + var book = await bookRepository.GetBook(id); + if (book == null) + { + return Results.NotFound(); + } + var bookDTO = 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 Results.Ok(bookDTO); + } + #endregion + + #region Add book + public static async Task AddBook(IBookRepository bookRepository,IAuthorRepository authorRepository, BookPost model) + { + try + { + var author = await authorRepository.GetAuthor(model.AuthorId); + Book book = new Book + { + Title = model.Title, + AuthorId = model.AuthorId + }; + + + await bookRepository.AddBook(book); + + BookDTO book2 = new BookDTO + { + Id = book.Id, + Title = model.Title, + AuthorId = model.AuthorId, + Author = new AuthorDTO + { + Id = author.Id, + FirstName = author.FirstName, + LastName = author.LastName, + Email = author.Email + } + }; + + return TypedResults.Ok(book2); + } + + catch (Exception e) + { + return TypedResults.Problem(e.Message); + } + + } + #endregion + + #region Delete book + public static async Task DeleteBook(IBookRepository bookRepository, int id) + { + try + { + var book = await bookRepository.GetBook(id); + var result = await bookRepository.DeleteBook(id); + if (result) + { + return Results.Ok(new { When = DateTime.Now, Status = "Deleted", Title = book.Title, AuthorId = book.AuthorId }); + } + return Results.NotFound(); + } + catch (Exception e) + { + return TypedResults.Problem(e.Message); + } + + } + #endregion + + #region Update book + public static async Task UpdateBook(IBookRepository bookRepository, IAuthorRepository authorRepository, int id, BookPut model) + { + try + { + var book = await bookRepository.GetBook(id); + if (book == null) + { + return Results.NotFound(); + } + if (model.Title != null) book.Title = model.Title; + if (model.AuthorId != null) book.AuthorId = model.AuthorId.Value; + await bookRepository.UpdateBook(id, book); + return Results.Ok(book); + } + catch (Exception e) + { + return TypedResults.Problem(e.Message); + } + } + #endregion + } } diff --git a/exercise.webapi/Models/Author.cs b/exercise.webapi/Models/Author.cs index 9f47878..9073248 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(); + public List Books { get; set; } = new List(); } } diff --git a/exercise.webapi/Models/Book.cs b/exercise.webapi/Models/Book.cs index 9534929..ed76045 100644 --- a/exercise.webapi/Models/Book.cs +++ b/exercise.webapi/Models/Book.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations.Schema; +using exercise.webapi.DTO; namespace exercise.webapi.Models { diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 43dec56..ea294b4 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.ConfigureAuthorApi(); app.Run(); diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..26082ed --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,29 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; + +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(b => b.Books).FirstAsync(b => b.Id == id); + } + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..b0d7cf8 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -13,10 +13,44 @@ public BookRepository(DataContext db) _db = db; } + public async Task AddBook(Book book) + { + await _db.Books.AddAsync(book); + await _db.SaveChangesAsync(); + return book; + } + + public async Task DeleteBook(int id) + { + var target = await _db.Books.FindAsync(id); + _db.Books.Remove(target); + await _db.SaveChangesAsync(); + return true; + } + 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).FirstAsync(b => b.Id == id); + } + + public async Task UpdateBook(int id, Book book) + { + var existingBook = _db.Books.Find(id); + if (existingBook != null) + { + existingBook.Title = book.Title; + existingBook.AuthorId = book.AuthorId; + _db.Books.Update(book); + _db.SaveChanges(); + } + return book; + + } } } diff --git a/exercise.webapi/Repository/IAuthorRepository.cs b/exercise.webapi/Repository/IAuthorRepository.cs new file mode 100644 index 0000000..301169f --- /dev/null +++ b/exercise.webapi/Repository/IAuthorRepository.cs @@ -0,0 +1,12 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public interface IAuthorRepository + { + public Task> GetAllAuthors(); + + Task GetAuthor(int id); + + } +} diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index f860016..d230009 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -5,5 +5,10 @@ namespace exercise.webapi.Repository public interface IBookRepository { public Task> GetAllBooks(); + + Task GetBook(int id); + Task DeleteBook(int id); + Task AddBook(Book book); + Task UpdateBook(int id, Book book); } } diff --git a/exercise.webapi/appsettings.json b/exercise.webapi/appsettings.json index 63d13d3..c9c4c3c 100644 --- a/exercise.webapi/appsettings.json +++ b/exercise.webapi/appsettings.json @@ -7,8 +7,8 @@ }, "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnectionString": "Host=ep-winter-silence-a8e5oju7.eastus2.azure.neon.tech; Database=neondb; Username=neondb_owner; Password=npg_nBmYdDo0Lr3z;" + "ConnectionStrings": { + "DefaultConnectionString": "Host=ep-cold-waterfall-a9ntum65.gwc.azure.neon.tech;Database=neondb;Username=neondb_owner;Password=npg_AW3mplMX4dvc" - } + } } \ No newline at end of file diff --git a/exercise.webapi/exercise.webapi.csproj b/exercise.webapi/exercise.webapi.csproj index 0ff4269..152fea4 100644 --- a/exercise.webapi/exercise.webapi.csproj +++ b/exercise.webapi/exercise.webapi.csproj @@ -8,10 +8,15 @@ - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - +