From e4b5cdce2aba70faa6dfc993c17a6c7584c5d344 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Thu, 23 Jan 2025 11:14:30 +0100 Subject: [PATCH 1/4] core + extension1 --- exercise.webapi/DTO/AuthorDto.cs | 18 +++++ exercise.webapi/DTO/AuthorResponseDto.cs | 19 +++++ exercise.webapi/DTO/BookDto.cs | 19 +++++ exercise.webapi/DTO/BookPublisherDto.cs | 15 ++++ exercise.webapi/DTO/BookUpdateDto.cs | 9 +++ exercise.webapi/Data/DataContext.cs | 2 + exercise.webapi/Data/Seeder.cs | 26 +++++++ exercise.webapi/Endpoints/AuthorApi.cs | 38 ++++++++++ exercise.webapi/Endpoints/BookApi.cs | 75 ++++++++++++++++++- exercise.webapi/Endpoints/PublisherApi.cs | 33 ++++++++ exercise.webapi/Models/Author.cs | 2 - exercise.webapi/Models/Book.cs | 3 +- exercise.webapi/Models/Publisher.cs | 9 +++ exercise.webapi/Program.cs | 4 + .../Repository/AuthorRepository.cs | 27 +++++++ exercise.webapi/Repository/BookRepository.cs | 32 +++++++- .../Repository/IAuthorRepository.cs | 10 +++ exercise.webapi/Repository/IBookRepository.cs | 5 ++ .../Repository/IPublisherRepository.cs | 10 +++ .../Repository/PublisherRepository.cs | 25 +++++++ exercise.webapi/appsettings.json | 6 +- 21 files changed, 378 insertions(+), 9 deletions(-) create mode 100644 exercise.webapi/DTO/AuthorDto.cs create mode 100644 exercise.webapi/DTO/AuthorResponseDto.cs create mode 100644 exercise.webapi/DTO/BookDto.cs create mode 100644 exercise.webapi/DTO/BookPublisherDto.cs create mode 100644 exercise.webapi/DTO/BookUpdateDto.cs create mode 100644 exercise.webapi/Endpoints/AuthorApi.cs create mode 100644 exercise.webapi/Endpoints/PublisherApi.cs create mode 100644 exercise.webapi/Models/Publisher.cs create mode 100644 exercise.webapi/Repository/AuthorRepository.cs create mode 100644 exercise.webapi/Repository/IAuthorRepository.cs create mode 100644 exercise.webapi/Repository/IPublisherRepository.cs create mode 100644 exercise.webapi/Repository/PublisherRepository.cs diff --git a/exercise.webapi/DTO/AuthorDto.cs b/exercise.webapi/DTO/AuthorDto.cs new file mode 100644 index 0000000..edcc23e --- /dev/null +++ b/exercise.webapi/DTO/AuthorDto.cs @@ -0,0 +1,18 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class AuthorDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + + public AuthorDto(Author author) + { + FirstName = author.FirstName; + LastName = author.LastName; + Email = author.Email; + } + } +} diff --git a/exercise.webapi/DTO/AuthorResponseDto.cs b/exercise.webapi/DTO/AuthorResponseDto.cs new file mode 100644 index 0000000..5754e83 --- /dev/null +++ b/exercise.webapi/DTO/AuthorResponseDto.cs @@ -0,0 +1,19 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class AuthorResponseDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public List Books { get; set; } + public AuthorResponseDto(Author author) + { + FirstName = author.FirstName; + LastName = author.LastName; + Email = author.Email; + Books = author.Books.Select(book => new BookPublisherDto(book)).ToList(); + } + } +} diff --git a/exercise.webapi/DTO/BookDto.cs b/exercise.webapi/DTO/BookDto.cs new file mode 100644 index 0000000..a79fc66 --- /dev/null +++ b/exercise.webapi/DTO/BookDto.cs @@ -0,0 +1,19 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookDto + { + public string Title { get; set; } + public AuthorDto Author { get; set; } + public string Publisher { get; set; } + + public BookDto(Book book) + { + Title = book.Title; + Author = new AuthorDto(book.Author); + Publisher = book.Publisher.Name; + } + + } +} diff --git a/exercise.webapi/DTO/BookPublisherDto.cs b/exercise.webapi/DTO/BookPublisherDto.cs new file mode 100644 index 0000000..36f9f4c --- /dev/null +++ b/exercise.webapi/DTO/BookPublisherDto.cs @@ -0,0 +1,15 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookPublisherDto + { + public string Title { get; set; } + public string Publisher { get; set; } + public BookPublisherDto(Book book) + { + Title = book.Title; + Publisher = book.Publisher.Name; + } + } +} diff --git a/exercise.webapi/DTO/BookUpdateDto.cs b/exercise.webapi/DTO/BookUpdateDto.cs new file mode 100644 index 0000000..0d2cfac --- /dev/null +++ b/exercise.webapi/DTO/BookUpdateDto.cs @@ -0,0 +1,9 @@ +namespace exercise.webapi.DTO +{ + public class BookUpdateDto + { + public string Title { get; set; } + public int AuthorId { get; set; } + public int PublisherId { get; set; } + } +} diff --git a/exercise.webapi/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index b6be7a9..3b3828f 100644 --- a/exercise.webapi/Data/DataContext.cs +++ b/exercise.webapi/Data/DataContext.cs @@ -24,9 +24,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasData(seeder.Authors); modelBuilder.Entity().HasData(seeder.Books); + modelBuilder.Entity().HasData(seeder.Publishers); } public DbSet Authors { get; set; } public DbSet Books { get; set; } + public DbSet Publishers { get; set; } } } diff --git a/exercise.webapi/Data/Seeder.cs b/exercise.webapi/Data/Seeder.cs index 955e3c8..b471215 100644 --- a/exercise.webapi/Data/Seeder.cs +++ b/exercise.webapi/Data/Seeder.cs @@ -76,15 +76,31 @@ public class Seeder "Flowers", "Leopards" }; + private List _publisherStrings = new List() + { + "Penguin", + "Random House", + "Harper Collins", + "Simon & Schuster", + "Macmillan", + "Hachette", + "Pearson", + "Scholastic", + "Bloomsbury", + "Oxford University Press" + }; private List _authors = new List(); private List _books = new List(); + private List _publishers = new List(); + public Seeder() { Random authorRandom = new Random(); Random bookRandom = new Random(); + Random publisherRand = new Random(); @@ -106,12 +122,22 @@ public Seeder() book.Title = $"{_firstword[bookRandom.Next(_firstword.Count)]} {_secondword[bookRandom.Next(_secondword.Count)]} {_thirdword[bookRandom.Next(_thirdword.Count)]}"; book.AuthorId = _authors[authorRandom.Next(_authors.Count)].Id; //book.Author = authors[book.AuthorId-1]; + book.PublisherId = publisherRand.Next(1, 11); _books.Add(book); } + for(int z = 1; z < 11; z++) + { + Publisher publisher = new Publisher(); + publisher.Id = z; + publisher.Name = _publisherStrings[z - 1]; + _publishers.Add(publisher); + } + } public List Authors { get { return _authors; } } public List Books { get { return _books; } } + public List Publishers { get { return _publishers; } } } } diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs new file mode 100644 index 0000000..94582c3 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,38 @@ +using exercise.webapi.DTO; +using exercise.webapi.Repository; + +namespace exercise.webapi.Endpoints +{ + public static class AuthorApi + { + public static void ConfigureAuthorsApi(this WebApplication app) + { + var authors = app.MapGroup("authors"); + authors.MapGet("/{id}", GetAuthor); + authors.MapGet("/", GetAuthors); + } + + + private static async Task GetAuthor(IAuthorRepository authorRepository,IPublisherRepository publisherRepository, int id) + { + var author = await authorRepository.GetAuthor(id); + if (author == null) + { + return Results.NotFound(); + } + foreach (var book in author.Books) + { + book.Publisher = await publisherRepository.GetPublisher(book.PublisherId); + } + + return TypedResults.Ok(new AuthorResponseDto(author)); + } + + private static async Task GetAuthors(IAuthorRepository authorRepository) + { + var authors = await authorRepository.GetAuthors(); + var authorDtos = authors.Select(author => new AuthorResponseDto(author)); + return TypedResults.Ok(authorDtos); + } + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..dc18039 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,5 +1,7 @@ -using exercise.webapi.Models; +using exercise.webapi.DTO; +using exercise.webapi.Models; using exercise.webapi.Repository; +using Microsoft.AspNetCore.Mvc; using static System.Reflection.Metadata.BlobBuilder; namespace exercise.webapi.Endpoints @@ -9,12 +11,81 @@ public static class BookApi public static void ConfigureBooksApi(this WebApplication app) { app.MapGet("/books", GetBooks); + app.MapGet("/books/{id}", GetBook); + app.MapPut("/books/{id}", UpdateBook); + app.MapDelete("/books/{id}", DeleteBook); + app.MapPost("/books", AddBook); } private static async Task GetBooks(IBookRepository bookRepository) { var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + var bookDtos = books.Select(book => new BookDto(book)); + return TypedResults.Ok(bookDtos); + } + + private static async Task GetBook(IBookRepository bookRepository, int id) + { + var book = await bookRepository.GetBook(id); + if (book == null) + { + return Results.NotFound(); + } + return TypedResults.Ok(new BookDto(book)); + } + + private static async Task UpdateBook(IBookRepository bookRepository, int id, BookUpdateDto updates) + { + var book = await bookRepository.GetBook(id); + if (book == null) + { + return Results.NotFound(); + } + book.AuthorId = updates.AuthorId; + book.Title = updates.Title; + book.PublisherId = updates.PublisherId; + + await bookRepository.UpdateBook(book); + Book updatedBook = await bookRepository.GetBook(book.Id); + return TypedResults.Ok(new BookDto(updatedBook)); + } + + private static async Task DeleteBook(IBookRepository bookRepository, int id) + { + var success = await bookRepository.DeleteBook(id); + if (!success) + { + return TypedResults.NotFound(); + } + return TypedResults.Ok("Successfully deleted!"); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task AddBook(IBookRepository bookRepository, IAuthorRepository authorRepository, IPublisherRepository publisherRepository, BookUpdateDto bookUpdateDto) + { + var author = await authorRepository.GetAuthor(bookUpdateDto.AuthorId); + var publisher = await publisherRepository.GetPublisher(bookUpdateDto.PublisherId); + if (author == null || publisher == null) + { + return TypedResults.NotFound("Author or publisher not found"); + } + if (string.IsNullOrWhiteSpace(bookUpdateDto.Title)) + { + return TypedResults.BadRequest("Invalid title"); + } + var book = new Book + { + AuthorId = bookUpdateDto.AuthorId, + Title = bookUpdateDto.Title, + PublisherId = bookUpdateDto.PublisherId, + Publisher = publisher, + Author = author + }; + await bookRepository.AddBook(book); + return TypedResults.Created($"https://localhost/7054/books/{book.Id}",new BookDto(book)); + } } } diff --git a/exercise.webapi/Endpoints/PublisherApi.cs b/exercise.webapi/Endpoints/PublisherApi.cs new file mode 100644 index 0000000..ed0ff79 --- /dev/null +++ b/exercise.webapi/Endpoints/PublisherApi.cs @@ -0,0 +1,33 @@ +using exercise.webapi.Repository; + +namespace exercise.webapi.Endpoints +{ + public static class PublisherApi + { + public static void ConfigurePublishersApi(this WebApplication app) + { + var publishers = app.MapGroup("publishers"); + publishers.MapGet("/{id}", GetPublisher); + publishers.MapGet("/", GetPublishers); + } + + + private static async Task GetPublisher(IPublisherRepository publisherRepository, int id) + { + var publisher = await publisherRepository.GetPublisher(id); + if (publisher == null) + { + return Results.NotFound(); + } + return TypedResults.Ok(publisher.Name); + } + + + private static async Task GetPublishers(IPublisherRepository publisherRepository) + { + var publishers = await publisherRepository.GetPublishers(); + var publisherNames = publishers.Select(publisher => (publisher.Name)); + return TypedResults.Ok(publisherNames); + } + } +} 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..eea8322 100644 --- a/exercise.webapi/Models/Book.cs +++ b/exercise.webapi/Models/Book.cs @@ -6,8 +6,9 @@ public class Book { public int Id { get; set; } public string Title { get; set; } - public int AuthorId { get; set; } + public int PublisherId { get; set; } public Author Author { get; set; } + public Publisher Publisher { get; set; } } } diff --git a/exercise.webapi/Models/Publisher.cs b/exercise.webapi/Models/Publisher.cs new file mode 100644 index 0000000..98a7276 --- /dev/null +++ b/exercise.webapi/Models/Publisher.cs @@ -0,0 +1,9 @@ +namespace exercise.webapi.Models +{ + public class Publisher + { + public int Id { get; set; } + public string Name { get; set; } + public ICollection Books { get; set; } = new List(); + } +} diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 43dec56..5896cb4 100644 --- a/exercise.webapi/Program.cs +++ b/exercise.webapi/Program.cs @@ -11,6 +11,8 @@ builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Library")); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); @@ -29,4 +31,6 @@ app.UseHttpsRedirection(); app.ConfigureBooksApi(); +app.ConfigureAuthorsApi(); +app.ConfigurePublishersApi(); app.Run(); diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..e487115 --- /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 GetAuthor(int id) + { + + return await _db.Authors.Include(a => a.Books).FirstOrDefaultAsync(a => a.Id == id); + } + + public async Task> GetAuthors() + { + return await _db.Authors.Include(a => a.Books).ToListAsync(); + } + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..3c38958 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -15,8 +15,38 @@ public BookRepository(DataContext db) public async Task> GetAllBooks() { - return await _db.Books.Include(b => b.Author).ToListAsync(); + return await _db.Books.Include(b => b.Author).Include(b => b.Publisher).ToListAsync(); } + + public async Task GetBook(int id) + { + return await _db.Books.Include(b => b.Author).Include(b => b.Publisher).FirstOrDefaultAsync(b => b.Id == id); + } + + public async Task UpdateBook(Book book) + { + _db.Books.Update(book); + await _db.SaveChangesAsync(); + return book; + } + + public async Task DeleteBook(int id) + { + var book = await _db.Books.FirstOrDefaultAsync(b => b.Id == id); + if (book == null) + { + return false; + } + _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..c80139e --- /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 GetAuthor(int id); + public Task> GetAuthors(); + } +} diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index f860016..7146fcf 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(); + public Task GetBook(int id); + + public Task UpdateBook(Book book); + public Task DeleteBook(int id); + public Task AddBook(Book book); } } diff --git a/exercise.webapi/Repository/IPublisherRepository.cs b/exercise.webapi/Repository/IPublisherRepository.cs new file mode 100644 index 0000000..e44fc0c --- /dev/null +++ b/exercise.webapi/Repository/IPublisherRepository.cs @@ -0,0 +1,10 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public interface IPublisherRepository + { + public Task GetPublisher(int id); + public Task> GetPublishers(); + } +} diff --git a/exercise.webapi/Repository/PublisherRepository.cs b/exercise.webapi/Repository/PublisherRepository.cs new file mode 100644 index 0000000..ee9f862 --- /dev/null +++ b/exercise.webapi/Repository/PublisherRepository.cs @@ -0,0 +1,25 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.webapi.Repository +{ + public class PublisherRepository : IPublisherRepository + { + DataContext _db; + public PublisherRepository(DataContext db) + { + _db = db; + } + + public async Task GetPublisher(int id) + { + return await _db.Publishers.Include(p => p.Books).FirstOrDefaultAsync(p => p.Id == id); + } + + public async Task> GetPublishers() + { + return await _db.Publishers.Include(p => p.Books).ToListAsync(); + } + } +} diff --git a/exercise.webapi/appsettings.json b/exercise.webapi/appsettings.json index 63d13d3..860039d 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-morning-rice-a926sulq.gwc.azure.neon.tech; Database=introexercise; Username=neondb_owner; Password=npg_NUJ2IPBDjs7V;" - } + } } \ No newline at end of file From d3320d2c115f565c070932cebcee81c66d875b75 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke <111982215+andreascnits@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:07:56 +0100 Subject: [PATCH 2/4] Update appsettings.json --- exercise.webapi/appsettings.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/exercise.webapi/appsettings.json b/exercise.webapi/appsettings.json index 860039d..8b13789 100644 --- a/exercise.webapi/appsettings.json +++ b/exercise.webapi/appsettings.json @@ -1,14 +1 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnectionString": "Host=ep-morning-rice-a926sulq.gwc.azure.neon.tech; Database=introexercise; Username=neondb_owner; Password=npg_NUJ2IPBDjs7V;" - - } -} \ No newline at end of file From bddcaaabf4dd952000aa3853d5c52a9027b223d0 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke <111982215+andreascnits@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:08:08 +0100 Subject: [PATCH 3/4] Update appsettings.Development.json --- exercise.webapi/appsettings.Development.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/exercise.webapi/appsettings.Development.json b/exercise.webapi/appsettings.Development.json index 63d13d3..8b13789 100644 --- a/exercise.webapi/appsettings.Development.json +++ b/exercise.webapi/appsettings.Development.json @@ -1,14 +1 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnectionString": "Host=ep-winter-silence-a8e5oju7.eastus2.azure.neon.tech; Database=neondb; Username=neondb_owner; Password=npg_nBmYdDo0Lr3z;" - - } -} \ No newline at end of file From 8f5947294feddc04331f06503da390be5a22c066 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Thu, 23 Jan 2025 13:50:40 +0100 Subject: [PATCH 4/4] extension 2+3 --- .gitignore | 4 +- exercise.webapi/DTO/BookAuthorDto.cs | 15 +++++ exercise.webapi/DTO/BookDto.cs | 4 +- exercise.webapi/DTO/BookUpdateDto.cs | 6 +- exercise.webapi/Data/DataContext.cs | 2 + exercise.webapi/Data/Seeder.cs | 20 +++++- exercise.webapi/Endpoints/AuthorApi.cs | 4 -- exercise.webapi/Endpoints/BookApi.cs | 65 ++++++++++++++----- exercise.webapi/Models/Author.cs | 1 + exercise.webapi/Models/Book.cs | 4 +- exercise.webapi/Models/BookAuthor.cs | 11 ++++ exercise.webapi/Program.cs | 6 ++ .../Repository/AuthorRepository.cs | 19 +++++- .../Repository/BookAuthorRepository.cs | 32 +++++++++ exercise.webapi/Repository/BookRepository.cs | 25 +++++-- .../Repository/IBookAuthorRepository.cs | 10 +++ 16 files changed, 188 insertions(+), 40 deletions(-) create mode 100644 exercise.webapi/DTO/BookAuthorDto.cs create mode 100644 exercise.webapi/Models/BookAuthor.cs create mode 100644 exercise.webapi/Repository/BookAuthorRepository.cs create mode 100644 exercise.webapi/Repository/IBookAuthorRepository.cs 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/BookAuthorDto.cs b/exercise.webapi/DTO/BookAuthorDto.cs new file mode 100644 index 0000000..75eb127 --- /dev/null +++ b/exercise.webapi/DTO/BookAuthorDto.cs @@ -0,0 +1,15 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class BookAuthorDto + { + public string Title { get; set; } + public string Author { get; set; } + public BookAuthorDto(string title, string author) + { + Title = title; + Author = author; + } + } +} diff --git a/exercise.webapi/DTO/BookDto.cs b/exercise.webapi/DTO/BookDto.cs index a79fc66..fabf609 100644 --- a/exercise.webapi/DTO/BookDto.cs +++ b/exercise.webapi/DTO/BookDto.cs @@ -5,13 +5,13 @@ namespace exercise.webapi.DTO public class BookDto { public string Title { get; set; } - public AuthorDto Author { get; set; } + public List Authors { get; set; } public string Publisher { get; set; } public BookDto(Book book) { Title = book.Title; - Author = new AuthorDto(book.Author); + Authors = book.Authors.Select(a => new AuthorDto(a)).ToList(); Publisher = book.Publisher.Name; } diff --git a/exercise.webapi/DTO/BookUpdateDto.cs b/exercise.webapi/DTO/BookUpdateDto.cs index 0d2cfac..8b1f6f1 100644 --- a/exercise.webapi/DTO/BookUpdateDto.cs +++ b/exercise.webapi/DTO/BookUpdateDto.cs @@ -2,8 +2,8 @@ { public class BookUpdateDto { - public string Title { get; set; } - public int AuthorId { get; set; } - public int PublisherId { get; set; } + public string? Title { get; set; } + //public List? AuthorIds { get; set; } + public int? PublisherId { get; set; } } } diff --git a/exercise.webapi/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index 3b3828f..45ea03b 100644 --- a/exercise.webapi/Data/DataContext.cs +++ b/exercise.webapi/Data/DataContext.cs @@ -25,10 +25,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasData(seeder.Authors); modelBuilder.Entity().HasData(seeder.Books); modelBuilder.Entity().HasData(seeder.Publishers); + modelBuilder.Entity().HasData(seeder.BookAuthors); } public DbSet Authors { get; set; } public DbSet Books { get; set; } public DbSet Publishers { get; set; } + public DbSet BookAuthors { get; set; } } } diff --git a/exercise.webapi/Data/Seeder.cs b/exercise.webapi/Data/Seeder.cs index b471215..2525068 100644 --- a/exercise.webapi/Data/Seeder.cs +++ b/exercise.webapi/Data/Seeder.cs @@ -93,6 +93,7 @@ public class Seeder private List _authors = new List(); private List _books = new List(); private List _publishers = new List(); + private List _bookAuthors = new List(); public Seeder() @@ -101,6 +102,7 @@ public Seeder() Random authorRandom = new Random(); Random bookRandom = new Random(); Random publisherRand = new Random(); + //Random bookAuthorRandom = new Random(); @@ -120,8 +122,7 @@ public Seeder() Book book = new Book(); book.Id = y; book.Title = $"{_firstword[bookRandom.Next(_firstword.Count)]} {_secondword[bookRandom.Next(_secondword.Count)]} {_thirdword[bookRandom.Next(_thirdword.Count)]}"; - book.AuthorId = _authors[authorRandom.Next(_authors.Count)].Id; - //book.Author = authors[book.AuthorId-1]; + //book.Authors = new List { _authors[authorRandom.Next(_authors.Count)] }; book.PublisherId = publisherRand.Next(1, 11); _books.Add(book); } @@ -134,10 +135,25 @@ public Seeder() _publishers.Add(publisher); } + //Randomly assign authors to books, they should have at least one author, but can have more + foreach (var book in _books) + { + int numberOfAuthors = authorRandom.Next(1, 4); + for (int i = 0; i < numberOfAuthors; i++) + { + BookAuthor bookAuthor = new BookAuthor(); + bookAuthor.Id = _bookAuthors.Count + 1; + bookAuthor.AuthorId = authorRandom.Next(1, 250); + bookAuthor.BookId = book.Id; + _bookAuthors.Add(bookAuthor); + } + } + } public List Authors { get { return _authors; } } public List Books { get { return _books; } } public List Publishers { get { return _publishers; } } + public List BookAuthors { get { return _bookAuthors; } } } } diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs index 94582c3..35a9a8d 100644 --- a/exercise.webapi/Endpoints/AuthorApi.cs +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -20,10 +20,6 @@ private static async Task GetAuthor(IAuthorRepository authorRepository, { return Results.NotFound(); } - foreach (var book in author.Books) - { - book.Publisher = await publisherRepository.GetPublisher(book.PublisherId); - } return TypedResults.Ok(new AuthorResponseDto(author)); } diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index dc18039..37346a8 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -15,6 +15,8 @@ public static void ConfigureBooksApi(this WebApplication app) app.MapPut("/books/{id}", UpdateBook); app.MapDelete("/books/{id}", DeleteBook); app.MapPost("/books", AddBook); + app.MapPost("/books/{id}/authors/{authorId}", AddAuthorToBook); + app.MapDelete("/books/{id}/authors/{authorId}", RemoveAuthorFromBook); } private static async Task GetBooks(IBookRepository bookRepository) @@ -41,12 +43,15 @@ private static async Task UpdateBook(IBookRepository bookRepository, in { return Results.NotFound(); } - book.AuthorId = updates.AuthorId; - book.Title = updates.Title; - book.PublisherId = updates.PublisherId; + + //book.AuthorId = updates.AuthorId; + if(!string.IsNullOrWhiteSpace(updates.Title)) + book.Title = updates.Title; + if (updates.PublisherId != null ) + book.PublisherId = (int)updates.PublisherId; await bookRepository.UpdateBook(book); - Book updatedBook = await bookRepository.GetBook(book.Id); + Book updatedBook = await bookRepository.GetBook(id); return TypedResults.Ok(new BookDto(updatedBook)); } @@ -65,27 +70,57 @@ private static async Task DeleteBook(IBookRepository bookRepository, in [ProducesResponseType(StatusCodes.Status404NotFound)] private static async Task AddBook(IBookRepository bookRepository, IAuthorRepository authorRepository, IPublisherRepository publisherRepository, BookUpdateDto bookUpdateDto) { - var author = await authorRepository.GetAuthor(bookUpdateDto.AuthorId); - var publisher = await publisherRepository.GetPublisher(bookUpdateDto.PublisherId); - if (author == null || publisher == null) + if (bookUpdateDto.PublisherId == null) { - return TypedResults.NotFound("Author or publisher not found"); + return TypedResults.BadRequest("PublisherId is required"); } - if (string.IsNullOrWhiteSpace(bookUpdateDto.Title)) + var publisher = await publisherRepository.GetPublisher((int)bookUpdateDto.PublisherId); + if (publisher == null) { - return TypedResults.BadRequest("Invalid title"); + return TypedResults.NotFound(); } var book = new Book { - AuthorId = bookUpdateDto.AuthorId, Title = bookUpdateDto.Title, - PublisherId = bookUpdateDto.PublisherId, - Publisher = publisher, - Author = author + PublisherId = (int)bookUpdateDto.PublisherId }; await bookRepository.AddBook(book); - return TypedResults.Created($"https://localhost/7054/books/{book.Id}",new BookDto(book)); + return TypedResults.Created($"https://localhost/7054/books/{book.Id}", new BookDto(book)); + } + + private static async Task AddAuthorToBook(IBookRepository bookRepository, IAuthorRepository authorRepository, IBookAuthorRepository bookAuthorRepository, int id, int authorId) + { + var book = await bookRepository.GetBook(id); + var author = await authorRepository.GetAuthor(authorId); + if (book == null || author == null) + { + return TypedResults.NotFound(); + } + var BookAuthor = new BookAuthor + { + BookId = book.Id, + AuthorId = author.Id + }; + bookAuthorRepository.AddBookAuthor(BookAuthor); + return TypedResults.Ok(new BookAuthorDto(book.Title, author.FirstName + author.LastName)); + } + + private static async Task RemoveAuthorFromBook(IBookRepository bookRepository, IAuthorRepository authorRepository, IBookAuthorRepository bookAuthorRepository, int id, int authorId) + { + var book = await bookRepository.GetBook(id); + var author = await authorRepository.GetAuthor(authorId); + if (book == null || author == null) + { + return TypedResults.NotFound(); + } + var bookAuthor = book.BookAuthors.FirstOrDefault(ba => ba.AuthorId == author.Id); + if (bookAuthor == null) + { + return TypedResults.NotFound(); + } + bookAuthorRepository.DeleteBookAuthor(bookAuthor.Id); + return TypedResults.Ok(); } } } diff --git a/exercise.webapi/Models/Author.cs b/exercise.webapi/Models/Author.cs index 6da6f42..7199a52 100644 --- a/exercise.webapi/Models/Author.cs +++ b/exercise.webapi/Models/Author.cs @@ -8,6 +8,7 @@ public class Author public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } + public List BookAuthors { get; set; } = new List(); public ICollection Books { get; set; } = new List(); } } diff --git a/exercise.webapi/Models/Book.cs b/exercise.webapi/Models/Book.cs index eea8322..fb76e97 100644 --- a/exercise.webapi/Models/Book.cs +++ b/exercise.webapi/Models/Book.cs @@ -6,9 +6,9 @@ public class Book { public int Id { get; set; } public string Title { get; set; } - public int AuthorId { get; set; } public int PublisherId { get; set; } - public Author Author { get; set; } + public List BookAuthors { get; set; } + public List Authors { get; set; } public Publisher Publisher { get; set; } } } diff --git a/exercise.webapi/Models/BookAuthor.cs b/exercise.webapi/Models/BookAuthor.cs new file mode 100644 index 0000000..66eae81 --- /dev/null +++ b/exercise.webapi/Models/BookAuthor.cs @@ -0,0 +1,11 @@ +namespace exercise.webapi.Models +{ + public class BookAuthor + { + public int Id { get; set; } + public int BookId { get; set; } + public int AuthorId { get; set; } + public Book Book { get; set; } + public Author Author { get; set; } + } +} diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 5896cb4..89d311e 100644 --- a/exercise.webapi/Program.cs +++ b/exercise.webapi/Program.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using exercise.webapi.Data; using exercise.webapi.Endpoints; using exercise.webapi.Repository; @@ -13,7 +14,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddDbContext(options => { + //options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnectionString")); + options.LogTo(message => Debug.WriteLine(message)); +}); var app = builder.Build(); using (var dbContext = new DataContext(new DbContextOptions())) diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs index e487115..8a222ea 100644 --- a/exercise.webapi/Repository/AuthorRepository.cs +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -15,13 +15,26 @@ public AuthorRepository(DataContext db) public async Task GetAuthor(int id) { - - return await _db.Authors.Include(a => a.Books).FirstOrDefaultAsync(a => a.Id == id); + var author = await _db.Authors + .Include(a => a.BookAuthors) + .ThenInclude(ba => ba.Book) + .ThenInclude(b => b.Publisher) + .FirstOrDefaultAsync(a => a.Id == id); + author.Books = author.BookAuthors.Select(ba => ba.Book).ToList(); + return author; } + + public async Task> GetAuthors() { - return await _db.Authors.Include(a => a.Books).ToListAsync(); + var authors = await _db.Authors + .Include(a => a.BookAuthors) + .ThenInclude(ba => ba.Book) + .ThenInclude(b => b.Publisher) + .ToListAsync(); + authors.ForEach(a => a.Books = a.BookAuthors.Select(ba => ba.Book).ToList()); + return authors; } } } diff --git a/exercise.webapi/Repository/BookAuthorRepository.cs b/exercise.webapi/Repository/BookAuthorRepository.cs new file mode 100644 index 0000000..1d3efae --- /dev/null +++ b/exercise.webapi/Repository/BookAuthorRepository.cs @@ -0,0 +1,32 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public class BookAuthorRepository : IBookAuthorRepository + { + DataContext _db; + public BookAuthorRepository(DataContext db) + { + _db = db; + } + public async Task AddBookAuthor(BookAuthor bookAuthor) + { + await _db.BookAuthors.AddAsync(bookAuthor); + await _db.SaveChangesAsync(); + return bookAuthor; + } + + public async Task DeleteBookAuthor(int id) + { + var bookAuthor = await _db.BookAuthors.FindAsync(id); + if (bookAuthor == null) + { + return false; + } + _db.BookAuthors.Remove(bookAuthor); + await _db.SaveChangesAsync(); + return true; + } + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 3c38958..0fcf064 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -4,10 +4,10 @@ namespace exercise.webapi.Repository { - public class BookRepository: IBookRepository + public class BookRepository : IBookRepository { DataContext _db; - + public BookRepository(DataContext db) { _db = db; @@ -15,15 +15,26 @@ public BookRepository(DataContext db) public async Task> GetAllBooks() { - return await _db.Books.Include(b => b.Author).Include(b => b.Publisher).ToListAsync(); - + var books = await _db.Books + .Include(b => b.BookAuthors) + .ThenInclude(ba => ba.Author) + .Include(b => b.Publisher) + .ToListAsync(); + books.ForEach(b => b.Authors = b.BookAuthors.Select(ba => ba.Author).ToList()); + return books; } public async Task GetBook(int id) { - return await _db.Books.Include(b => b.Author).Include(b => b.Publisher).FirstOrDefaultAsync(b => b.Id == id); + var book = await _db.Books + .Include(b => b.BookAuthors) + .ThenInclude(ba => ba.Author) + .Include(b => b.Publisher) + .FirstOrDefaultAsync(b => b.Id == id); + book.Authors = book.BookAuthors.Select(ba => ba.Author).ToList(); + return book; } - + public async Task UpdateBook(Book book) { _db.Books.Update(book); @@ -33,7 +44,7 @@ public async Task UpdateBook(Book book) public async Task DeleteBook(int id) { - var book = await _db.Books.FirstOrDefaultAsync(b => b.Id == id); + var book = await _db.Books.FindAsync(id); if (book == null) { return false; diff --git a/exercise.webapi/Repository/IBookAuthorRepository.cs b/exercise.webapi/Repository/IBookAuthorRepository.cs new file mode 100644 index 0000000..1df8108 --- /dev/null +++ b/exercise.webapi/Repository/IBookAuthorRepository.cs @@ -0,0 +1,10 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public interface IBookAuthorRepository + { + public Task DeleteBookAuthor(int id); + public Task AddBookAuthor(BookAuthor bookAuthor); + } +}