From 5bfc4ccfe7b82d73cea1d1ad5040ca7ab17208c6 Mon Sep 17 00:00:00 2001 From: Kristian Sylte Date: Thu, 23 Jan 2025 14:42:47 +0100 Subject: [PATCH] core + extension 1 --- exercise.webapi/Data/DataContext.cs | 16 ++--- exercise.webapi/Data/Seeder.cs | 48 ++++++++----- exercise.webapi/Endpoints/AuthorAPI.cs | 31 +++++++++ exercise.webapi/Endpoints/BookApi.cs | 67 ++++++++++++++++++- exercise.webapi/Endpoints/PublisherApi.cs | 31 +++++++++ exercise.webapi/Models/Author.cs | 1 - exercise.webapi/Models/AuthorDTOs.cs | 36 ++++++++++ exercise.webapi/Models/Book.cs | 20 +++--- exercise.webapi/Models/BookDTOs.cs | 53 +++++++++++++++ exercise.webapi/Models/Publisher.cs | 8 +++ exercise.webapi/Models/PublisherDTOs.cs | 26 +++++++ exercise.webapi/Program.cs | 5 +- .../Repository/AuthorRepository.cs | 28 ++++++++ exercise.webapi/Repository/BookRepository.cs | 60 ++++++++++++++--- .../Repository/IAuthorRepository.cs | 9 +++ exercise.webapi/Repository/IBookRepository.cs | 13 ++-- .../Repository/IPublisherRepository.cs | 9 +++ .../Repository/PublisherRepository.cs | 30 +++++++++ 18 files changed, 440 insertions(+), 51 deletions(-) create mode 100644 exercise.webapi/Endpoints/AuthorAPI.cs create mode 100644 exercise.webapi/Endpoints/PublisherApi.cs create mode 100644 exercise.webapi/Models/AuthorDTOs.cs create mode 100644 exercise.webapi/Models/BookDTOs.cs create mode 100644 exercise.webapi/Models/Publisher.cs create mode 100644 exercise.webapi/Models/PublisherDTOs.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/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index b6be7a9..7155d95 100644 --- a/exercise.webapi/Data/DataContext.cs +++ b/exercise.webapi/Data/DataContext.cs @@ -1,17 +1,15 @@ -using exercise.webapi.Models; -using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection.Emit; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; namespace exercise.webapi.Data { public class DataContext : DbContext { + public DataContext(DbContextOptions options) + : base(options) { } - public DataContext(DbContextOptions options) : base(options) - { - - } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); @@ -24,9 +22,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasData(seeder.Authors); modelBuilder.Entity().HasData(seeder.Books); - + modelBuilder.Entity().HasData(seeder.Publisher); } + public DbSet Authors { get; set; } public DbSet Books { get; set; } + public DbSet Publisher { get; set; } } } diff --git a/exercise.webapi/Data/Seeder.cs b/exercise.webapi/Data/Seeder.cs index 955e3c8..58a6516 100644 --- a/exercise.webapi/Data/Seeder.cs +++ b/exercise.webapi/Data/Seeder.cs @@ -15,7 +15,7 @@ public class Seeder "Mick", "Kate", "Charles", - "Kate" + "Kate", }; private List _lastnames = new List() { @@ -28,8 +28,7 @@ public class Seeder "Jagger", "Winslet", "Windsor", - "Middleton" - + "Middleton", }; private List _domain = new List() { @@ -42,7 +41,7 @@ public class Seeder "gov.us", "gov.gr", "gov.nl", - "gov.ru" + "gov.ru", }; private List _firstword = new List() { @@ -52,9 +51,7 @@ public class Seeder "Fifteen", "A bunch of", "An army of", - "A herd of" - - + "A herd of", }; private List _secondword = new List() { @@ -65,7 +62,7 @@ public class Seeder "Green", "Transparent", "Rose Smelling", - "Bitter" + "Bitter", }; private List _thirdword = new List() { @@ -74,19 +71,25 @@ public class Seeder "Planets", "Houses", "Flowers", - "Leopards" + "Leopards", }; 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(); - + for (int i = 1; i < 30; i++) + { + Publisher publisher = new Publisher(); + publisher.Id = i; + publisher.Name = $"publisher {i}"; + _publishers.Add(publisher); + } for (int x = 1; x < 250; x++) { @@ -94,24 +97,35 @@ public Seeder() author.Id = x; author.FirstName = _firstnames[authorRandom.Next(_firstnames.Count)]; author.LastName = _lastnames[authorRandom.Next(_lastnames.Count)]; - author.Email = $"{author.FirstName}.{author.LastName}@{_domain[authorRandom.Next(_domain.Count)]}".ToLower(); + author.Email = + $"{author.FirstName}.{author.LastName}@{_domain[authorRandom.Next(_domain.Count)]}".ToLower(); _authors.Add(author); } - for (int y = 1; y < 250; y++) { 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.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[authorRandom.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 Publisher + { + get { return _publishers; } } - public List Authors { get { return _authors; } } - public List Books { get { return _books; } } } } diff --git a/exercise.webapi/Endpoints/AuthorAPI.cs b/exercise.webapi/Endpoints/AuthorAPI.cs new file mode 100644 index 0000000..5238a08 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorAPI.cs @@ -0,0 +1,31 @@ +using exercise.webapi.Models; +using exercise.webapi.Repository; +using static System.Reflection.Metadata.BlobBuilder; + +namespace exercise.webapi.Endpoints; + +public static class AuthorApi +{ + public static void ConfigureAuthorsApi(this WebApplication app) + { + app.MapGet("/author", GetAll); + app.MapGet("/author/{id}", Get); + } + + private static async Task GetAll(IAuthorRepository repo) + { + var result = await repo.GetAll(); + var authors = result.Select(a => new AuthorDTO(a)); + return TypedResults.Ok(authors); + } + + private static async Task Get(IAuthorRepository repo, int id) + { + var result = await repo.Get(id); + if (result == null) + { + return TypedResults.NotFound("Author with given id not found"); + } + return TypedResults.Ok(new AuthorDTO(result)); + } +} diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..30fdfb3 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -9,12 +9,77 @@ public static class BookApi public static void ConfigureBooksApi(this WebApplication app) { app.MapGet("/books", GetBooks); + app.MapGet("/books/{id}", GetBook); + app.MapGet("/books/delete/{id}", DeleteBook); + app.MapPost("/books/update", UpdateBook); + app.MapPost("/books/add", CreateBook); } private static async Task GetBooks(IBookRepository bookRepository) { var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + return TypedResults.Ok(books.Select(b => new BookDTO(b))); + } + + private static async Task GetBook(IBookRepository bookRepository, int id) + { + var book = await bookRepository.GetBook(id); + if (book == null) + { + return TypedResults.NotFound("No book with give id"); + } + return TypedResults.Ok(new BookDTO(book)); + } + + private static async Task CreateBook( + IBookRepository bookRepository, + BookPostDTO book + ) + { + try + { + var result = await bookRepository.CreateBook( + new Book + { + AuthorId = book.AuthorId, + Title = book.Title, + PublisherId = book.PublisherId, + } + ); + if (result == null) + { + return TypedResults.NotFound("Author or publisher not found"); + } + return TypedResults.Ok(new BookDTO(result)); + } + catch (Exception e) + { + return TypedResults.BadRequest(e); + } + } + + private static async Task DeleteBook(IBookRepository repository, int id) + { + var deleted = await repository.DeleteBook(id); + if (!deleted) + { + return TypedResults.NotFound("No book with given id"); + } + return TypedResults.Ok(); + } + + private static async Task UpdateBook( + IBookRepository repo, + int bookId, + int authorId + ) + { + var result = await repo.UpdateAuthor(bookId, authorId); + if (result == null) + { + return TypedResults.NotFound("Book or author with given id does not exist"); + } + return TypedResults.Ok(new BookDTO(result)); } } } diff --git a/exercise.webapi/Endpoints/PublisherApi.cs b/exercise.webapi/Endpoints/PublisherApi.cs new file mode 100644 index 0000000..a6651c8 --- /dev/null +++ b/exercise.webapi/Endpoints/PublisherApi.cs @@ -0,0 +1,31 @@ +using exercise.webapi.Models; +using exercise.webapi.Repository; +using static System.Reflection.Metadata.BlobBuilder; + +namespace exercise.webapi.Endpoints; + +public static class PublisherApi +{ + public static void ConfigurePublisherApi(this WebApplication app) + { + app.MapGet("/publisher", GetAll); + app.MapGet("/publisher/{id}", Get); + } + + private static async Task GetAll(IPublisherRepository repo) + { + var result = await repo.GetAll(); + var authors = result.Select(p => new PublisherDTO(p)); + return TypedResults.Ok(authors); + } + + private static async Task Get(IPublisherRepository repo, int id) + { + var result = await repo.Get(id); + if (result == null) + { + return TypedResults.NotFound("Publisher with given id not found"); + } + return TypedResults.Ok(new PublisherDTO(result)); + } +} diff --git a/exercise.webapi/Models/Author.cs b/exercise.webapi/Models/Author.cs index 9f47878..e2c35a6 100644 --- a/exercise.webapi/Models/Author.cs +++ b/exercise.webapi/Models/Author.cs @@ -9,7 +9,6 @@ public class Author 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/AuthorDTOs.cs b/exercise.webapi/Models/AuthorDTOs.cs new file mode 100644 index 0000000..22583d7 --- /dev/null +++ b/exercise.webapi/Models/AuthorDTOs.cs @@ -0,0 +1,36 @@ +namespace exercise.webapi.Models; + +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 Books { get; set; } = new List(); + + public AuthorDTO(Author author) + { + this.Id = author.Id; + this.FirstName = author.FirstName; + this.LastName = author.LastName; + this.Email = author.Email; + this.Books = author.Books.Select(b => new BookStrippedDTO(b)).ToList(); + } +} + +public class AuthorStrippedDTO +{ + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + + public AuthorStrippedDTO(Author author) + { + this.Id = author.Id; + this.FirstName = author.FirstName; + this.LastName = author.LastName; + this.Email = author.Email; + } +} diff --git a/exercise.webapi/Models/Book.cs b/exercise.webapi/Models/Book.cs index 9534929..32571b0 100644 --- a/exercise.webapi/Models/Book.cs +++ b/exercise.webapi/Models/Book.cs @@ -1,13 +1,15 @@ using System.ComponentModel.DataAnnotations.Schema; -namespace exercise.webapi.Models +namespace exercise.webapi.Models; + +public class Book { - 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 Id { get; set; } + public string Title { get; set; } + + public int PublisherId { get; set; } + public Publisher Publisher { get; set; } + + public int AuthorId { get; set; } + public Author Author { get; set; } } diff --git a/exercise.webapi/Models/BookDTOs.cs b/exercise.webapi/Models/BookDTOs.cs new file mode 100644 index 0000000..665a1e5 --- /dev/null +++ b/exercise.webapi/Models/BookDTOs.cs @@ -0,0 +1,53 @@ +namespace exercise.webapi.Models; + +public class BookPostDTO +{ + public string Title { get; set; } + public int AuthorId { get; set; } + public int PublisherId { get; set; } +} + +public class BookDTO +{ + public int Id { get; set; } + public string Title { get; set; } + + public AuthorStrippedDTO Author { get; set; } + public PublisherStrippedDto Publisher { get; set; } + + public BookDTO(Book book) + { + this.Id = book.Id; + this.Title = book.Title; + this.Author = new AuthorStrippedDTO(book.Author); + this.Publisher = new PublisherStrippedDto(book.Publisher); + } +} + +public class BookStrippedNoPublisherDTO +{ + public int Id { get; set; } + public string Title { get; set; } + public AuthorStrippedDTO Author { get; set; } + + public BookStrippedNoPublisherDTO(Book book) + { + this.Id = book.Id; + this.Title = book.Title; + this.Author = new AuthorStrippedDTO(book.Author); + } +} + +public class BookStrippedDTO +{ + public int Id { get; set; } + public string Title { get; set; } + public PublisherStrippedDto Publisher { get; set; } + + public BookStrippedDTO(Book book) + { + this.Id = book.Id; + this.Title = book.Title; + this.Publisher = new PublisherStrippedDto(book.Publisher); + } +} diff --git a/exercise.webapi/Models/Publisher.cs b/exercise.webapi/Models/Publisher.cs new file mode 100644 index 0000000..ebf56e2 --- /dev/null +++ b/exercise.webapi/Models/Publisher.cs @@ -0,0 +1,8 @@ +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/Models/PublisherDTOs.cs b/exercise.webapi/Models/PublisherDTOs.cs new file mode 100644 index 0000000..3ec98ad --- /dev/null +++ b/exercise.webapi/Models/PublisherDTOs.cs @@ -0,0 +1,26 @@ +namespace exercise.webapi.Models; + +public class PublisherDTO +{ + public string Name { get; set; } + public ICollection Books { get; set; } = + new List(); + + public PublisherDTO(Publisher publisher) + { + this.Name = publisher.Name; + this.Books = publisher.Books.Select(b => new BookStrippedNoPublisherDTO(b)).ToList(); + } +} + +public class PublisherStrippedDto +{ + public int Id { get; set; } + public string Name { get; set; } + + public PublisherStrippedDto(Publisher publisher) + { + this.Id = publisher.Id; + this.Name = publisher.Name; + } +} diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 43dec56..48e9051 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(); @@ -19,7 +21,6 @@ dbContext.Database.EnsureCreated(); } - // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -29,4 +30,6 @@ app.UseHttpsRedirection(); app.ConfigureBooksApi(); +app.ConfigureAuthorsApi(); +app.ConfigurePublisherApi(); app.Run(); diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..fa15d38 --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,28 @@ +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 Get(int id) + { + return await _db + .Authors.Include(b => b.Books) + .ThenInclude(b => b.Publisher) + .FirstOrDefaultAsync(b => b.Id == id); + } + + public async Task> GetAll() + { + return await _db.Authors.Include(b => b.Books).ThenInclude(b => b.Publisher).ToListAsync(); + } +} diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..c815dac 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -2,21 +2,63 @@ using exercise.webapi.Models; using Microsoft.EntityFrameworkCore; -namespace exercise.webapi.Repository +namespace exercise.webapi.Repository; + +public class BookRepository : IBookRepository { - public class BookRepository: IBookRepository + DataContext _db; + + public BookRepository(DataContext db) + { + _db = db; + } + + public async Task CreateBook(Book book) { - DataContext _db; - - public BookRepository(DataContext db) + var result = _db.Books.Add(book); + await _db.SaveChangesAsync(); + return await GetBook(result.Entity.Id); + } + + public async Task DeleteBook(int id) + { + var result = await _db.Books.FindAsync(id); + if (result == null) { - _db = db; + return false; } + _db.Books.Remove(result); + await _db.SaveChangesAsync(); + return true; + } - public async Task> GetAllBooks() - { - return await _db.Books.Include(b => b.Author).ToListAsync(); + public async Task> GetAllBooks() + { + 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 UpdateAuthor(int bookId, int authorId) + { + var author = await _db.Authors.FindAsync(authorId); + if (author == null) + { + return null; + } + var entity = await _db.Books.FindAsync(bookId); + if (entity == null) + { + return null; } + entity.AuthorId = authorId; + await _db.SaveChangesAsync(); + return entity; } } diff --git a/exercise.webapi/Repository/IAuthorRepository.cs b/exercise.webapi/Repository/IAuthorRepository.cs new file mode 100644 index 0000000..392c7e4 --- /dev/null +++ b/exercise.webapi/Repository/IAuthorRepository.cs @@ -0,0 +1,9 @@ +namespace exercise.webapi.Repository; + +using exercise.webapi.Models; + +public interface IAuthorRepository +{ + public Task> GetAll(); + public Task Get(int id); +} diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index f860016..98feee9 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -1,9 +1,12 @@ using exercise.webapi.Models; -namespace exercise.webapi.Repository +namespace exercise.webapi.Repository; + +public interface IBookRepository { - public interface IBookRepository - { - public Task> GetAllBooks(); - } + public Task> GetAllBooks(); + public Task GetBook(int id); + public Task UpdateAuthor(int bookId, int authorId); + public Task DeleteBook(int id); + public Task CreateBook(Book book); } diff --git a/exercise.webapi/Repository/IPublisherRepository.cs b/exercise.webapi/Repository/IPublisherRepository.cs new file mode 100644 index 0000000..c534c1f --- /dev/null +++ b/exercise.webapi/Repository/IPublisherRepository.cs @@ -0,0 +1,9 @@ +namespace exercise.webapi.Repository; + +using exercise.webapi.Models; + +public interface IPublisherRepository +{ + public Task> GetAll(); + public Task Get(int id); +} diff --git a/exercise.webapi/Repository/PublisherRepository.cs b/exercise.webapi/Repository/PublisherRepository.cs new file mode 100644 index 0000000..fb0e7b3 --- /dev/null +++ b/exercise.webapi/Repository/PublisherRepository.cs @@ -0,0 +1,30 @@ +namespace exercise.webapi.Repository; + +using System.Collections.Generic; +using System.Threading.Tasks; +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; + +public class PublisherRepository : IPublisherRepository +{ + DataContext _db; + + public PublisherRepository(DataContext db) + { + _db = db; + } + + public async Task Get(int id) + { + return await _db + .Publisher.Include(p => p.Books) + .ThenInclude(b => b.Author) + .FirstOrDefaultAsync(p => p.Id == id); + } + + public async Task> GetAll() + { + return await _db.Publisher.Include(p => p.Books).ThenInclude(b => b.Author).ToListAsync(); + } +}