diff --git a/.gitignore b/.gitignore index b357a0c..836b809 100644 --- a/.gitignore +++ b/.gitignore @@ -368,4 +368,6 @@ FodyWeavers.xsd */**/obj/Release */Migrations /exercise.wwwapi/appsettings.json + /exercise.wwwapi/appsettings.Development.json + diff --git a/exercise.webapi/DTO/AuthorDTO.cs b/exercise.webapi/DTO/AuthorDTO.cs new file mode 100644 index 0000000..0eb8e2d --- /dev/null +++ b/exercise.webapi/DTO/AuthorDTO.cs @@ -0,0 +1,25 @@ +using System; +using exercise.webapi.Models; + +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 ICollection> PublisherBooks {get;set;} = new List>(); + + public AuthorDTO(Author author) + { + Id = author.Id; + FirstName = author.FirstName; + LastName = author.LastName; + Email = author.Email; + foreach (Book b in author.Books) + { + PublisherBooks.Add(new Tuple(b.Title, b.Publisher.Name)); + } + } +} diff --git a/exercise.webapi/DTO/AuthorGetBookDTO.cs b/exercise.webapi/DTO/AuthorGetBookDTO.cs new file mode 100644 index 0000000..685b409 --- /dev/null +++ b/exercise.webapi/DTO/AuthorGetBookDTO.cs @@ -0,0 +1,21 @@ +using System; +using exercise.webapi.Models; + +namespace exercise.webapi.DTO; + +public class AuthorGetBookDTO +{ + + public int Id {get; set;} + public string FirstName {get;set;} + public string LastName {get;set;} + public string Email {get;set;} + + public AuthorGetBookDTO(Author author) + { + Id = author.Id; + FirstName = author.FirstName; + LastName = author.LastName; + Email = author.Email; + } +} diff --git a/exercise.webapi/DTO/AuthorPostDTO.cs b/exercise.webapi/DTO/AuthorPostDTO.cs new file mode 100644 index 0000000..830baa9 --- /dev/null +++ b/exercise.webapi/DTO/AuthorPostDTO.cs @@ -0,0 +1,8 @@ +using System; + +namespace exercise.webapi.DTO; + +public class AuthorPostDTO +{ + public int Id {get; set;} +} diff --git a/exercise.webapi/DTO/BookDTO.cs b/exercise.webapi/DTO/BookDTO.cs new file mode 100644 index 0000000..8fcb1c5 --- /dev/null +++ b/exercise.webapi/DTO/BookDTO.cs @@ -0,0 +1,28 @@ +using System; +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 string PublisherName {get; set;} + public AuthorGetBookDTO Author { get; set; } + + + public BookDTO(Book book) + { + Id = book.Id; + Title = book.Title; + AuthorId = book.AuthorId; + PublisherName = book.Publisher.Name; + Author = new AuthorGetBookDTO(book.Author); + + + + + } +} diff --git a/exercise.webapi/DTO/BookGetAuthorDTO.cs b/exercise.webapi/DTO/BookGetAuthorDTO.cs new file mode 100644 index 0000000..61e3a10 --- /dev/null +++ b/exercise.webapi/DTO/BookGetAuthorDTO.cs @@ -0,0 +1,16 @@ +using System; +using exercise.webapi.Models; + +namespace exercise.webapi.DTO; + +public class BookGetAuthorDTO +{ + public int Id {get;set;} + public string Title {get;set;} + + public BookGetAuthorDTO(Book b) + { + Id = b.Id; + Title = b.Title; + } +} diff --git a/exercise.webapi/DTO/CreateBookDTO.cs b/exercise.webapi/DTO/CreateBookDTO.cs new file mode 100644 index 0000000..a334aa9 --- /dev/null +++ b/exercise.webapi/DTO/CreateBookDTO.cs @@ -0,0 +1,10 @@ +using System; + +namespace exercise.webapi.DTO; + +public class CreateBookDTO +{ + public string Title { get; set; } + + public int AuthorId { get; set; } +} diff --git a/exercise.webapi/DTO/PublisherGetDTO.cs b/exercise.webapi/DTO/PublisherGetDTO.cs new file mode 100644 index 0000000..b04d0de --- /dev/null +++ b/exercise.webapi/DTO/PublisherGetDTO.cs @@ -0,0 +1,23 @@ +using System; +using exercise.webapi.Models; + +namespace exercise.webapi.DTO; + +public class PublisherGetDTO +{ + + public int Id {get;set;} + public string Name {get;set;} + public List Books {get;set;} = new List(); + + + public PublisherGetDTO(Publisher p) + { + Id = p.Id; + Name = p.Name; + foreach (Book b in p.Books) + { + Books.Add(new BookDTO(b)); + } + } +} diff --git a/exercise.webapi/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index b6be7a9..4a57af4 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..9906d0b 100644 --- a/exercise.webapi/Data/Seeder.cs +++ b/exercise.webapi/Data/Seeder.cs @@ -77,16 +77,29 @@ public class Seeder "Leopards" }; + private List _publisher = new List() + { + "Hot Taco", "Beer", "Hotsauce inc.", "Git Gabble" + }; + 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 publisherRandom = new Random(); - + for (int z = 1; z < 50; z++) + { + Publisher p = new Publisher(); + p.Id = z; + p.Name = $"{_firstword[publisherRandom.Next(_firstword.Count)]} {_publisher[publisherRandom.Next(_publisher.Count)]}"; + _publishers.Add(p); + } for (int x = 1; x < 250; x++) { @@ -105,13 +118,17 @@ public Seeder() 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.PublisherId = _publishers[publisherRandom.Next(_publishers.Count)].Id; //book.Author = authors[book.AuthorId-1]; _books.Add(book); } + + } 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..1cb8089 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,43 @@ +using System; +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Payload; +using exercise.webapi.Repository; +using Microsoft.AspNetCore.Mvc; + +namespace exercise.webapi.Endpoints; + +public static class AuthorApi +{ + + public static void ConfigureAuthorApi(this WebApplication app) + { + var author = app.MapGroup("/author"); + + author.MapGet("/{id}", GetAuthor); + author.MapGet("/", GetAuthors); + + + } + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetAuthor(IAuthorRepository authorRepository, int id) + { + Payload payload = await authorRepository.GetAuthor(id); + + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public static async Task GetAuthors(IAuthorRepository authorRepository) + { + Payload> payload = await authorRepository.GetAuthors(); + + return TypedResults.Ok(payload.Data); + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..00b1583 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,5 +1,8 @@ -using exercise.webapi.Models; +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Payload; using exercise.webapi.Repository; +using Microsoft.AspNetCore.Mvc; using static System.Reflection.Metadata.BlobBuilder; namespace exercise.webapi.Endpoints @@ -8,13 +11,77 @@ 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}", UpdateAuthor); + books.MapDelete("/{id}", DeleteBook); + books.MapPost("/", CreateBook); } + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] private static async Task GetBooks(IBookRepository bookRepository) { - var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + Payload> payload = await bookRepository.GetAllBooks(); + return TypedResults.Ok(payload.Data); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task GetBook(IBookRepository bookRepository, int id) + { + Payload payload = await bookRepository.GetBook(id); + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task UpdateAuthor(IBookRepository bookRepository, int bookId, AuthorPostDTO authorPost) + { + Payload payload = await bookRepository.UpdateAuthor(bookId, authorPost); + + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task DeleteBook(IBookRepository bookRepository, int id) + { + Payload payload = await bookRepository.DeleteBook(id); + + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task CreateBook(IBookRepository bookRepository, CreateBookDTO createBookDTO) + { + Payload payload = await bookRepository.CreateBook(createBookDTO); + + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + + } } } diff --git a/exercise.webapi/Endpoints/PublisherAPI.cs b/exercise.webapi/Endpoints/PublisherAPI.cs new file mode 100644 index 0000000..c1e3381 --- /dev/null +++ b/exercise.webapi/Endpoints/PublisherAPI.cs @@ -0,0 +1,51 @@ +using System; +using System.Xml.Serialization; +using exercise.webapi.DTO; +using exercise.webapi.Payload; +using exercise.webapi.Repository; +using Microsoft.AspNetCore.Mvc; + +namespace exercise.webapi.Endpoints; + +public static class PublisherAPI +{ + + public static void ConfigurePublisherApi(this WebApplication app) + { + var publisher = app.MapGroup("/publishers"); + + publisher.MapGet("/", GetPublishers); + publisher.MapGet("/{id}", GetPublisher); + + + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetPublishers(IPublisherRepository publisherRepository) + { + Payload> payload = await publisherRepository.GetPublishers(); + + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + + } + + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public static async Task GetPublisher(IPublisherRepository publisherRepository, int id) + { + Payload payload = await publisherRepository.GetPublisher(id); + + if (!payload.GoodResponse) + { + return TypedResults.NotFound(payload.Message); + } + + return TypedResults.Ok(payload.Data); + } +} 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/Models/Book.cs b/exercise.webapi/Models/Book.cs index 9534929..f9ea6d2 100644 --- a/exercise.webapi/Models/Book.cs +++ b/exercise.webapi/Models/Book.cs @@ -9,5 +9,7 @@ public class Book public int AuthorId { get; set; } public Author Author { get; set; } + public int PublisherId {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..52cc3cc --- /dev/null +++ b/exercise.webapi/Models/Publisher.cs @@ -0,0 +1,11 @@ +using System; + +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/Payload/Payload.cs b/exercise.webapi/Payload/Payload.cs new file mode 100644 index 0000000..1ee789d --- /dev/null +++ b/exercise.webapi/Payload/Payload.cs @@ -0,0 +1,11 @@ +using System; + +namespace exercise.webapi.Payload; + +public class Payload where T : class +{ + public T Data; + public bool GoodResponse = true; + public string Message = "Success"; + +} diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 43dec56..52a8592 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,7 @@ app.UseHttpsRedirection(); app.ConfigureBooksApi(); +app.ConfigureAuthorApi(); +app.ConfigurePublisherApi(); app.Run(); + diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..d600856 --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using exercise.webapi.Data; +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Payload; +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) + { + Payload payload = new Payload(); + Author author = await _db.Authors.Include(a => a.Books).ThenInclude(b => b.Publisher).FirstOrDefaultAsync(a => a.Id == id); + + + try + { + AuthorDTO authorDTO = new AuthorDTO(author); + payload.Data = authorDTO; + return payload; + } + catch (Exception) + { + payload.GoodResponse = false; + payload.Message = $"Could not find author with id={id}!"; + + return payload; + } + } + + public async Task>> GetAuthors() + { + Payload> payload = new Payload>(); + List authors = await _db.Authors.Include(a => a.Books).ThenInclude(b => b.Publisher).ToListAsync(); + + List authorDTOs = new List(); + + foreach (Author a in authors) + { + authorDTOs.Add(new AuthorDTO(a)); + } + + payload.Data = authorDTOs; + + return payload; + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..357f817 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -1,5 +1,8 @@ using exercise.webapi.Data; +using exercise.webapi.DTO; using exercise.webapi.Models; +using exercise.webapi.Payload; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.EntityFrameworkCore; namespace exercise.webapi.Repository @@ -13,10 +16,131 @@ public BookRepository(DataContext db) _db = db; } - public async Task> GetAllBooks() + public async Task>> GetAllBooks() { - return await _db.Books.Include(b => b.Author).ToListAsync(); + Payload> payload = new Payload>(); + List books = await _db.Books.Include(b => b.Author).Include(b => b.Publisher).ToListAsync(); + List bookDTOs = new List(); + foreach (Book b in books) + { + bookDTOs.Add(new BookDTO(b)); + } + payload.Data = bookDTOs; + return payload; + } + + public async Task> GetBook(int id) + { + Payload payload = new Payload(); + Book book = await _db.Books.Include(b => b.Author).Include(b => b.Publisher).FirstOrDefaultAsync(b => b.Id == id); + try + { + payload.Data = new BookDTO(book); + return payload; + } + catch (Exception) + { + payload.GoodResponse = false; + payload.Message = $"Could not find book with id={id}"; + return payload; + } + } + + public async Task> UpdateAuthor(int bookId, AuthorPostDTO authorPost) + { + Payload payload = new Payload(); + Book book = await _db.Books.Include(b => b.Author).Include(b => b.Publisher).FirstOrDefaultAsync(b => b.Id == bookId); + if (book != null) + { + // Found book + Author newAuthor = await _db.Authors.Include(a => a.Books).FirstOrDefaultAsync(a => a.Id == authorPost.Id); + if (newAuthor != null) + { + // Found book and the new author + Author oldAuthor = book.Author; + book.Author = newAuthor; + + // Update the books of each of the authors + oldAuthor.Books.Remove(book); + newAuthor.Books.Add(book); + + payload.Data = new BookDTO(book); + await _db.SaveChangesAsync(); + return payload; + + } + + else + { + //The new author not found + payload.GoodResponse = false; + payload.Message = $"Could not find author with id = {authorPost.Id}"; + return payload; + + } + } + else + { + // Book not found + payload.GoodResponse = false; + payload.Message = $"Could not find book with id = {bookId}"; + return payload; + } + + } + + public async Task> DeleteBook(int id) + { + Payload payload = new Payload(); + Book book = await _db.Books.Include(b => b.Author).Include(b => b.Publisher).FirstOrDefaultAsync(b => b.Id == id); + if (book != null) + { + Author author = book.Author; + author.Books.Remove(book); + _db.Books.Remove(book); + payload.Data = new BookDTO(book); + await _db.SaveChangesAsync(); + + return payload; + } + + payload.GoodResponse = false; + payload.Message = $"Book with id={id} not found"; + return payload; } + + + public async Task> CreateBook(CreateBookDTO bookDTO) + { + Payload payload = new Payload(); + + Author author = await _db.Authors.FindAsync(bookDTO.AuthorId); + + if (author != null) + { + Book book = new Book(); + book.Title = bookDTO.Title; + book.AuthorId = bookDTO.AuthorId; + book.Author = author; + + _db.Add(book); + author.Books.Add(book); + + await _db.SaveChangesAsync(); + payload.Data = new BookDTO(book); + return payload; + } + + + payload.GoodResponse = false; + payload.Message = $"Author with id={bookDTO.AuthorId} not found!"; + + return payload; + + + } + + } } diff --git a/exercise.webapi/Repository/IAuthorRepository.cs b/exercise.webapi/Repository/IAuthorRepository.cs new file mode 100644 index 0000000..f2d6431 --- /dev/null +++ b/exercise.webapi/Repository/IAuthorRepository.cs @@ -0,0 +1,11 @@ +using System; +using exercise.webapi.DTO; +using exercise.webapi.Payload; + +namespace exercise.webapi.Repository; + +public interface IAuthorRepository +{ + public Task>> GetAuthors(); + public Task> GetAuthor(int id); +} diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index f860016..b6a4d6a 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; +using exercise.webapi.Models; +using exercise.webapi.Payload; namespace exercise.webapi.Repository { public interface IBookRepository { - public Task> GetAllBooks(); + public Task>> GetAllBooks(); + public Task> GetBook(int id); + public Task> UpdateAuthor(int bookId, AuthorPostDTO authorPost); + public Task> DeleteBook(int id); + public Task> CreateBook(CreateBookDTO bookDTO); } } diff --git a/exercise.webapi/Repository/IPublisherRepository.cs b/exercise.webapi/Repository/IPublisherRepository.cs new file mode 100644 index 0000000..4e7c9da --- /dev/null +++ b/exercise.webapi/Repository/IPublisherRepository.cs @@ -0,0 +1,12 @@ +using System; +using exercise.webapi.DTO; +using exercise.webapi.Payload; + +namespace exercise.webapi.Repository; + +public interface IPublisherRepository +{ + public Task>> GetPublishers(); + + public Task> GetPublisher(int id); +} diff --git a/exercise.webapi/Repository/PublisherRepository.cs b/exercise.webapi/Repository/PublisherRepository.cs new file mode 100644 index 0000000..c0f72f3 --- /dev/null +++ b/exercise.webapi/Repository/PublisherRepository.cs @@ -0,0 +1,56 @@ +using System; +using exercise.webapi.Data; +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Payload; +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) + { + Payload payload = new Payload(); + Publisher publisher = await _db.Publishers.Include(p => p.Books).ThenInclude(b => b.Author).FirstOrDefaultAsync(pub => pub.Id == id); + + try + { + payload.Data = new PublisherGetDTO(publisher); + return payload; + } + catch (Exception) + { + payload.GoodResponse = false; + payload.Message = $"Could not find publisher with id={id}"; + return payload; + } + + } + + public async Task>> GetPublishers() + { + Payload> payload = new Payload>(); + + List publishers = await _db.Publishers.Include(p => p.Books).ThenInclude(b => b.Author).ToListAsync(); + + List pgDTO = new List(); + + foreach (Publisher p in publishers) + { + pgDTO.Add(new PublisherGetDTO(p)); + } + + payload.Data = pgDTO; + + return payload; + } +}