diff --git a/.gitignore b/.gitignore index 46d3f4b..5c58dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -367,5 +367,5 @@ FodyWeavers.xsd */**/obj/Debug */**/obj/Release */Migrations -/workshop.wwwapi/appsettings.json -/workshop.wwwapi/appsettings.Development.json \ No newline at end of file +/exercise.wwwapi/appsettings.json +/exercise.wwwapi/appsettings.Development.json \ No newline at end of file diff --git a/exercise.webapi/DTO/AuthorPayload.cs b/exercise.webapi/DTO/AuthorPayload.cs new file mode 100644 index 0000000..89a16e2 --- /dev/null +++ b/exercise.webapi/DTO/AuthorPayload.cs @@ -0,0 +1,16 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class AuthorPayload + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + + public IEnumerable Books { get; set; } //= new List(); + + + } +} diff --git a/exercise.webapi/DTO/BookPaylaod.cs b/exercise.webapi/DTO/BookPaylaod.cs new file mode 100644 index 0000000..fcd9f62 --- /dev/null +++ b/exercise.webapi/DTO/BookPaylaod.cs @@ -0,0 +1,13 @@ +namespace exercise.webapi.DTO +{ + public class BookPaylaod where T : class + { + public T Data { get; set; } + + //public string FirstName { get; set; } + //public string LastName { get; set; } + //public string Email { get; set; } + + public string Message { get; set; } = "Status"; + } +} diff --git a/exercise.webapi/DTO/BookPost.cs b/exercise.webapi/DTO/BookPost.cs new file mode 100644 index 0000000..b29222e --- /dev/null +++ b/exercise.webapi/DTO/BookPost.cs @@ -0,0 +1,11 @@ +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..f9f6553 --- /dev/null +++ b/exercise.webapi/DTO/BookPut.cs @@ -0,0 +1,15 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookPut + { + /* + public int? Id { get; set; } + public string? Title { get; set; } + public Author Author { get; set; } + */ + + public required int AuthorId { get; set; } + } +} diff --git a/exercise.webapi/DTO/TestBookPayload.cs b/exercise.webapi/DTO/TestBookPayload.cs new file mode 100644 index 0000000..01efc96 --- /dev/null +++ b/exercise.webapi/DTO/TestBookPayload.cs @@ -0,0 +1,14 @@ +namespace exercise.webapi.DTO +{ + public class TestBookPayload + { + public int Id { get; set; } + public string Title { get; set; } + + public int? AuthorId { get; set; } + + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Email { get; set; } + } +} diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs new file mode 100644 index 0000000..0755f0b --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,64 @@ +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Repository; + +namespace exercise.webapi.Endpoints +{ + public static class AuthorApi + { + public static void ConfigureAuthorsApi(this WebApplication app) + { + var author = app.MapGroup("author"); + + author.MapGet("/", GetAllAuthors); + author.MapGet("/{id}", GetAuthorById); + } + + public static async Task GetAllAuthors(IRepository authorRepository) + { + + var payload = from a in authorRepository.GetAllEntities().Result + select new AuthorPayload() + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + Email = a.Email, + Books = from b in a.Books + select new TestBookPayload() + { + Id = b.Id, + Title = b.Title, + AuthorId = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + Email = a.Email + } + }; + + return TypedResults.Ok(payload); + } + + private static async Task GetAuthorById(IRepository authorRepository, int id) + { + + var author = await authorRepository.GetEntityById(id); + + var payload = new AuthorPayload() + { + Id = author.Id, + FirstName = author.FirstName, + LastName = author.LastName, + Email = author.Email, + Books = from b in author.Books + select new TestBookPayload() + { + Id = b.Id, + Title = b.Title + } + }; + + return TypedResults.Ok(payload); + } + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..941042c 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,121 @@ 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}", GetBookById); + books.MapPut("/{id}", UpdateBook); + books.MapDelete("/{id}", DeleteBook); + books.MapPost("/", CreateBook); + } + + private static async Task GetBooks(IRepository bookRepository) + { + + + var payload = from b in bookRepository.GetAllEntities().Result + select new TestBookPayload() + { + Id = b.Id, + Title = b.Title, + AuthorId = b.AuthorId, + FirstName = b.Author.FirstName, + LastName = b.Author.LastName, + Email = b.Author.Email + }; + + return TypedResults.Ok(payload); + } + + private static async Task GetBookById(IRepository bookRepository, int id) + { + var book = await bookRepository.GetEntityById(id); + + var payload = new TestBookPayload() + { + Id = book.Id, + Title = book.Title, + AuthorId = book.AuthorId, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + }; + + return TypedResults.Ok(payload); + } + + private static async Task UpdateBook(IRepository bookRepository, IRepository authorRepository, int id, BookPut model) + { + var book = await bookRepository.GetEntityById(id); + if (book == null) return TypedResults.NotFound("No book matching provided ID"); + + try + { + book.Author = await authorRepository.GetEntityById(model.AuthorId); + book.AuthorId = model.AuthorId; + + bookRepository.SaveRepository(); + } + catch (Exception ex) { return TypedResults.BadRequest(ex); } + + var payload = new TestBookPayload() + { + Id = book.Id, + Title = book.Title, + AuthorId = book.AuthorId, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + }; + + return TypedResults.Ok(payload); } - private static async Task GetBooks(IBookRepository bookRepository) + private static async Task DeleteBook(IRepository bookRepository, int id) { - var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + try + { + var book = await bookRepository.GetEntityById(id); + if (book == null) return TypedResults.NotFound("No book matching provided ID"); + + var status = await bookRepository.DeleteEntity(book); + bookRepository.SaveRepository(); + + return TypedResults.Ok(status); + + } catch (Exception ex) { return TypedResults.BadRequest(ex); } + } + + + private static async Task CreateBook(IRepository bookRepository, IRepository authorRepository, BookPost model) + { + try + { + var allBooks = await bookRepository.GetAllEntities(); + + Book book = new Book() + { + Id = allBooks.Max(b => b.Id) + 1, + Title = model.Title, + Author = await authorRepository.GetEntityById(model.AuthorId), + AuthorId = model.AuthorId + }; + + var payload = new TestBookPayload() + { + Id = book.Id, + Title = book.Title, + AuthorId = book.AuthorId, + FirstName = book.Author.FirstName, + LastName = book.Author.LastName, + Email = book.Author.Email + }; + + return TypedResults.Ok(payload); + + } + catch (Exception ex) { return TypedResults.BadRequest(ex); } } } } diff --git a/exercise.webapi/Models/Author.cs b/exercise.webapi/Models/Author.cs index 9f47878..5698331 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 + //[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..cc0d1b6 100644 --- a/exercise.webapi/Program.cs +++ b/exercise.webapi/Program.cs @@ -1,5 +1,6 @@ using exercise.webapi.Data; using exercise.webapi.Endpoints; +using exercise.webapi.Models; using exercise.webapi.Repository; using Microsoft.EntityFrameworkCore; @@ -10,7 +11,8 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Library")); -builder.Services.AddScoped(); +builder.Services.AddScoped, BookRepository>(); +builder.Services.AddScoped, AuthorRepository>(); var app = builder.Build(); @@ -29,4 +31,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..45a488c --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,36 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.webapi.Repository +{ + public class AuthorRepository : IRepository + { + DataContext _db; + + public AuthorRepository(DataContext db) + { + _db = db; + } + public async void SaveRepository() + { + await _db.SaveChangesAsync(); + } + + public async Task> GetAllEntities() + { + return await _db.Authors.Include(a => a.Books).ToListAsync(); + } + + public async Task GetEntityById(int id) + { + return await _db.Authors.Include(a => a.Books).FirstOrDefaultAsync(a => a.Id == id); + } + + public Task DeleteEntity(Author author) + { + throw new NotImplementedException(); + } + + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..73ecd5c 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -4,7 +4,7 @@ namespace exercise.webapi.Repository { - public class BookRepository: IBookRepository + public class BookRepository: IRepository { DataContext _db; @@ -13,10 +13,29 @@ public BookRepository(DataContext db) _db = db; } - public async Task> GetAllBooks() + public async void SaveRepository() + { + await _db.SaveChangesAsync(); + } + + public async Task> GetAllEntities() { return await _db.Books.Include(b => b.Author).ToListAsync(); } + + public async Task GetEntityById(int id) + { + return await _db.Books.Include(b => b.Author).FirstOrDefaultAsync(b => b.Id == id); + } + + public async Task DeleteEntity(Book book) + { + try + { + _db.Books.Remove(book); + return true; + }catch (Exception ex) {return false;} + } } } diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs deleted file mode 100644 index f860016..0000000 --- a/exercise.webapi/Repository/IBookRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -using exercise.webapi.Models; - -namespace exercise.webapi.Repository -{ - public interface IBookRepository - { - public Task> GetAllBooks(); - } -} diff --git a/exercise.webapi/Repository/IRepository.cs b/exercise.webapi/Repository/IRepository.cs new file mode 100644 index 0000000..ace14ee --- /dev/null +++ b/exercise.webapi/Repository/IRepository.cs @@ -0,0 +1,15 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public interface IRepository + { + public void SaveRepository(); + + public Task> GetAllEntities(); //Book + public Task GetEntityById(int id); //Book or Author + + public Task DeleteEntity(T entity); + + } +} 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 + + - +