diff --git a/.gitignore b/.gitignore index b357a0c..0062c87 100644 --- a/.gitignore +++ b/.gitignore @@ -367,5 +367,10 @@ FodyWeavers.xsd */**/obj/Debug */**/obj/Release */Migrations -/exercise.wwwapi/appsettings.json -/exercise.wwwapi/appsettings.Development.json +/exercise.webapi/appsettings.json +/exercise.webapi/appsettings.Development.json + +*/**/appsettings.json +*/**/appsettings.Development.json +exercise.webapi/appsettings.json +exercise.webapi/appsettings.Development.json 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/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 new file mode 100644 index 0000000..fabf609 --- /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 List Authors { get; set; } + public string Publisher { get; set; } + + public BookDto(Book book) + { + Title = book.Title; + Authors = book.Authors.Select(a => new AuthorDto(a)).ToList(); + 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..8b1f6f1 --- /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 List? AuthorIds { get; set; } + public int? PublisherId { get; set; } + } +} diff --git a/exercise.webapi/DTO/CheckoutDto.cs b/exercise.webapi/DTO/CheckoutDto.cs new file mode 100644 index 0000000..d5e1492 --- /dev/null +++ b/exercise.webapi/DTO/CheckoutDto.cs @@ -0,0 +1,18 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class CheckoutDto + { + public BookDto Book { get; set; } + public DateTime CheckoutDate { get; set; } + public DateTime DueDate { get; set; } + + public CheckoutDto(Checkout checkout) + { + Book = new BookDto(checkout.Book); + CheckoutDate = checkout.CheckoutDate; + DueDate = checkout.DueDate; + } + } +} diff --git a/exercise.webapi/DTO/OverdueDto.cs b/exercise.webapi/DTO/OverdueDto.cs new file mode 100644 index 0000000..f15f292 --- /dev/null +++ b/exercise.webapi/DTO/OverdueDto.cs @@ -0,0 +1,9 @@ +namespace exercise.webapi.DTO +{ + public class OverdueDto + { + public BookDto Book { get; set; } + public DateTime DueDate { get; set; } + public int DaysOverdue { get; set; } + } +} diff --git a/exercise.webapi/DTO/UnavailableDto.cs b/exercise.webapi/DTO/UnavailableDto.cs new file mode 100644 index 0000000..83178e9 --- /dev/null +++ b/exercise.webapi/DTO/UnavailableDto.cs @@ -0,0 +1,11 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.DTO +{ + public class UnavailableDto + { + public BookDto Book { get; set; } + public DateTime AvailableBy { get; set; } + + } +} diff --git a/exercise.webapi/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index b6be7a9..46274eb 100644 --- a/exercise.webapi/Data/DataContext.cs +++ b/exercise.webapi/Data/DataContext.cs @@ -24,9 +24,15 @@ 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); + modelBuilder.Entity().HasData(seeder.Checkouts); } public DbSet Authors { get; set; } public DbSet Books { get; set; } + public DbSet Publishers { get; set; } + public DbSet BookAuthors { get; set; } + public DbSet Checkouts { get; set; } } } diff --git a/exercise.webapi/Data/Seeder.cs b/exercise.webapi/Data/Seeder.cs index 955e3c8..bccc61d 100644 --- a/exercise.webapi/Data/Seeder.cs +++ b/exercise.webapi/Data/Seeder.cs @@ -76,15 +76,34 @@ 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(); + private List _bookAuthors = new List(); + private List _checkouts = new List(); + public Seeder() { Random authorRandom = new Random(); Random bookRandom = new Random(); + Random publisherRand = new Random(); + //Random bookAuthorRandom = new Random(); @@ -104,14 +123,53 @@ 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); } + for(int z = 1; z < 11; z++) + { + Publisher publisher = new Publisher(); + publisher.Id = z; + publisher.Name = _publisherStrings[z - 1]; + _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); + } + } + + + //Randomly check some books out. They should all be from various previous dates, some should be overdue, but not most. + Random checkoutRandom = new Random(); + for (int i = 1; i < 100; i++) + { + Checkout checkout = new Checkout(); + checkout.Id = i; + checkout.BookId = bookRandom.Next(1, 250); + checkout.CheckoutDate = DateTime.Now.AddDays(-checkoutRandom.Next(1, 365)); + checkout.DueDate = checkout.CheckoutDate.AddDays(14); + checkout.IsReturned = checkoutRandom.Next(0, 2) == 1 ? true : false; + _checkouts.Add(checkout); + } + } public List Authors { get { return _authors; } } public List Books { get { return _books; } } + public List Publishers { get { return _publishers; } } + public List BookAuthors { get { return _bookAuthors; } } + public List Checkouts { get { return _checkouts; } } } } diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs new file mode 100644 index 0000000..35a9a8d --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,34 @@ +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(); + } + + 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..7e98e22 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,133 @@ 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); + app.MapPost("/books/{id}/authors/{authorId}", AddAuthorToBook); + app.MapDelete("/books/{id}/authors/{authorId}", RemoveAuthorFromBook); + //Get unavailable books, we might use queryparameters and the checkoutrepository + app.MapGet("/books/unavailable", GetUnavailableBooks); } 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; + 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(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) + { + if (bookUpdateDto.PublisherId == null) + { + return TypedResults.BadRequest("PublisherId is required"); + } + var publisher = await publisherRepository.GetPublisher((int)bookUpdateDto.PublisherId); + if (publisher == null) + { + return TypedResults.NotFound(); + } + var book = new Book + { + Title = bookUpdateDto.Title, + PublisherId = (int)bookUpdateDto.PublisherId + }; + await bookRepository.AddBook(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(); + } + + private static async Task GetUnavailableBooks(ICheckoutRepository checkoutRepository, IBookRepository bookRepository) + { + var checkouts = await checkoutRepository.GetAllCheckouts(); + var unavailableBooks = checkouts + .Where(c => !c.IsReturned) + .Select(c => new UnavailableDto + { + Book = new BookDto(c.Book), + AvailableBy = c.DueDate, + }) + .ToList(); + + return TypedResults.Ok(unavailableBooks); } } } diff --git a/exercise.webapi/Endpoints/CheckoutApi.cs b/exercise.webapi/Endpoints/CheckoutApi.cs new file mode 100644 index 0000000..d0cd4ef --- /dev/null +++ b/exercise.webapi/Endpoints/CheckoutApi.cs @@ -0,0 +1,52 @@ +using exercise.webapi.DTO; +using exercise.webapi.Models; +using exercise.webapi.Repository; + +namespace exercise.webapi.Endpoints +{ + public static class CheckoutApi + { + public static void ConfigureCheckoutApi(this WebApplication app) + { + var checkout = app.MapGroup("checkout"); + checkout.MapPost("/{bookId}", Checkout); + checkout.MapGet("/overdue", GetOverdueBooks); + } + + private static async Task Checkout(ICheckoutRepository checkoutRepository,IBookRepository bookRepository, int bookId) + { + bool checkedOut = await checkoutRepository.IsCheckedOut(bookId); + if (checkedOut) + { + return TypedResults.Ok("Book is unavailable"); + } + Book book = await bookRepository.GetBook(bookId); + Checkout checkout = new Checkout + { + BookId = bookId, + DueDate = DateTime.Now.AddDays(14), + IsReturned = false, + CheckoutDate = DateTime.Now, + Book = book + }; + await checkoutRepository.AddCheckout(checkout); + return TypedResults.Ok(new CheckoutDto(checkout)); + } + + + public static async Task GetOverdueBooks(IBookRepository bookRepository, ICheckoutRepository checkoutRepository) + { + var checkouts = await checkoutRepository.GetAllCheckouts(); + var overdue = checkouts.Where(c => c.DueDate < DateTime.Now && !c.IsReturned).OrderBy(c => c.DueDate); + var overdueBooks = overdue.Select(c => new OverdueDto + { + Book = new BookDto(c.Book), + DueDate = c.DueDate, + DaysOverdue = (DateTime.Now - c.DueDate).Days + }); + return TypedResults.Ok(overdueBooks); + } + + + } +} 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..7199a52 100644 --- a/exercise.webapi/Models/Author.cs +++ b/exercise.webapi/Models/Author.cs @@ -8,8 +8,7 @@ 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 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 9534929..fb76e97 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 Author Author { get; set; } + public int PublisherId { 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/Models/Checkout.cs b/exercise.webapi/Models/Checkout.cs new file mode 100644 index 0000000..4503c17 --- /dev/null +++ b/exercise.webapi/Models/Checkout.cs @@ -0,0 +1,14 @@ +namespace exercise.webapi.Models +{ + public class Checkout + { + public int Id { get; set; } + public int BookId { get; set; } + public DateTime CheckoutDate { get; set; } + public bool IsReturned { get; set; } + public DateTime DueDate { get; set; } + public Book Book { 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..c097285 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; @@ -11,7 +12,15 @@ builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Library")); builder.Services.AddScoped(); +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())) @@ -29,4 +38,7 @@ app.UseHttpsRedirection(); app.ConfigureBooksApi(); +app.ConfigureAuthorsApi(); +app.ConfigurePublishersApi(); +app.ConfigureCheckoutApi(); app.Run(); diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..8a222ea --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,40 @@ +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) + { + 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() + { + 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 1f5e64a..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,8 +15,49 @@ public BookRepository(DataContext db) public async Task> GetAllBooks() { - return await _db.Books.Include(b => b.Author).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) + { + 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); + await _db.SaveChangesAsync(); + return book; + } + public async Task DeleteBook(int id) + { + var book = await _db.Books.FindAsync(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/CheckoutRepository.cs b/exercise.webapi/Repository/CheckoutRepository.cs new file mode 100644 index 0000000..764d69e --- /dev/null +++ b/exercise.webapi/Repository/CheckoutRepository.cs @@ -0,0 +1,68 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.webapi.Repository +{ + public class CheckoutRepository : ICheckoutRepository + { + DataContext _db; + public CheckoutRepository(DataContext db) + { + _db = db; + } + + + public async Task GetCheckout(int id) + { + return await _db.Checkouts + .Include(c => c.Book) + .ThenInclude(b => b.BookAuthors) + .ThenInclude(ba => ba.Author) + .Include(c => c.Book) + .ThenInclude(b => b.Publisher) + .FirstOrDefaultAsync(c => c.Id == id); + } + + + public async Task AddCheckout(Checkout checkout) + { + await _db.Checkouts.AddAsync(checkout); + await _db.SaveChangesAsync(); + return checkout; + } + + public async Task> GetAllCheckouts() + { + var checkouts = await _db.Checkouts + .Include(c => c.Book) + .ThenInclude(b => b.BookAuthors) + .ThenInclude(ba => ba.Author) + .Include(c => c.Book) + .ThenInclude(b => b.Publisher).ToListAsync(); + checkouts.ForEach(c => c.Book.Authors = c.Book.BookAuthors.Select(ba => ba.Author).ToList()); + return checkouts; + } + + public async Task DeleteCheckout(int id) + { + throw new NotImplementedException(); + } + + public async Task UpdateCheckout(Checkout checkout) + { + throw new NotImplementedException(); + + } + + public async Task IsCheckedOut(int bookId) + { + var checkout = await _db.Checkouts.FirstOrDefaultAsync(c => c.BookId == bookId && !c.IsReturned); + if (checkout == null) + { + return false; + } + return true; + } + } +} 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/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); + } +} 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/ICheckoutRepository.cs b/exercise.webapi/Repository/ICheckoutRepository.cs new file mode 100644 index 0000000..fc697d0 --- /dev/null +++ b/exercise.webapi/Repository/ICheckoutRepository.cs @@ -0,0 +1,15 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository +{ + public interface ICheckoutRepository + { + public Task> GetAllCheckouts(); + public Task GetCheckout(int id); + public Task AddCheckout(Checkout checkout); + public Task DeleteCheckout(int id); + public Task UpdateCheckout(Checkout checkout); + public Task IsCheckedOut(int bookId); + + } +} 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