diff --git a/ERD-base.png b/ERD-base.png new file mode 100644 index 00000000..9c4efdde Binary files /dev/null and b/ERD-base.png differ diff --git a/api-cinema-challenge/api-cinema-challenge.sln b/api-cinema-challenge/api-cinema-challenge.sln index 9cd490f5..efe4e74a 100644 --- a/api-cinema-challenge/api-cinema-challenge.sln +++ b/api-cinema-challenge/api-cinema-challenge.sln @@ -8,6 +8,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3C371BAA-344D-4C8A-AF08-7829816D726F}" ProjectSection(SolutionItems) = preProject ..\.gitignore = ..\.gitignore + api-cinema-challenge\ERD-base.png = api-cinema-challenge\ERD-base.png ..\README.md = ..\README.md EndProjectSection EndProject diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO.cs new file mode 100644 index 00000000..98819e04 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO.cs @@ -0,0 +1,11 @@ +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.DTO +{ + public class CustomerPost + { + public string name { get; set; } + public string email { get; set; } + public string phone { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO.cs new file mode 100644 index 00000000..d0007249 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO.cs @@ -0,0 +1,28 @@ +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.DTO +{ + public class MovieDTO : Dateing + { + public string Title { get; set; } + public string Rating { get; set; } + public string Description { get; set; } + public int RuntimeMins { get; set; } + public IEnumerable Screenings { get; set; } + + } + public class MovieSmall : Dateing + { + public string Title { get; set; } + public string Rating { get; set; } + public string Description { get; set; } + public int RuntimeMins { get; set; } + } + public class MoviePost + { + public string Title { get; set; } + public string Rating { get; set; } + public string Description { get; set; } + public int RuntimeMins { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO.cs new file mode 100644 index 00000000..da912400 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO.cs @@ -0,0 +1,28 @@ +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.DTO +{ + public class ScreeningDTO : Dateing + { + public int ScreenNumber { get; set; } + public int Capacity { get; set; } + public DateTime StartsAt { get; set; } + public int MovieId { get; set; } + } + public class ScreeningDTOBig : Dateing + { + public int ScreenNumber { get; set; } + public int Capacity { get; set; } + public DateTime StartsAt { get; set; } + public int MovieId { get; set; } + public MovieSmall Movie { get; set; } + + } + public class ScreeningPost + { + public int ScreenNumber { get; set; } + public int Capacity { get; set; } + public DateTime StartsAt { get; set; } + public int MovieId { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs b/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs index ad4fe854..8589e998 100644 --- a/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs +++ b/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs @@ -1,4 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using System.Net.Sockets; +using api_cinema_challenge.Models; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; namespace api_cinema_challenge.Data @@ -6,10 +8,11 @@ namespace api_cinema_challenge.Data public class CinemaContext : DbContext { private string _connectionString; - public CinemaContext(DbContextOptions options) : base(options) + public CinemaContext() { var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - _connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString")!; + _connectionString = configuration.GetValue("ConnectionStrings:DefaultConnection")!; + this.Database.SetConnectionString(_connectionString); this.Database.EnsureCreated(); } @@ -20,7 +23,19 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { + //Primary keys + modelBuilder.Entity().HasKey(c => c.Id); + modelBuilder.Entity().HasKey(m => m.Id); + modelBuilder.Entity().HasKey(s => s.Id); + + //Relations + modelBuilder.Entity() + .HasMany(m => m.Screenings) + .WithOne(m => m.Movie); } + public DbSet Customers { get; set; } + public DbSet Movies { get; set; } + public DbSet Screenings { get; set; } } } diff --git a/api-cinema-challenge/api-cinema-challenge/Data/Seeder.cs b/api-cinema-challenge/api-cinema-challenge/Data/Seeder.cs new file mode 100644 index 00000000..b3e5501b --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Data/Seeder.cs @@ -0,0 +1,40 @@ +using System; +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.Data +{ + public static class Seeder + { + public async static void SeedCinemaApi(this WebApplication app) + { + using (var db = new CinemaContext()) + { + if (!db.Customers.Any()) + { + db.Add(new Customer() { Name = "Nigel" }); + db.Add(new Customer() { Name = "Dave" }); + await db.SaveChangesAsync(); + } + if (!db.Movies.Any()) + { + db.Add(new Movie() { Title = "Cheese & Pineapple" }); + db.Add(new Movie() { Title = "Vegan Cheese Tastic" }); + await db.SaveChangesAsync(); + + } + if (!db.Screenings.Any()) + { + db.Add(new Screening() { MovieId = 1, StartsAt = DateTime.UtcNow.AddDays(1)}); + await db.SaveChangesAsync(); + } + + //order data + if (1 == 1) + { + + await db.SaveChangesAsync(); + } + } + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/EndPoint/CinemaEndpoint.cs b/api-cinema-challenge/api-cinema-challenge/EndPoint/CinemaEndpoint.cs new file mode 100644 index 00000000..8fe10e7c --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/EndPoint/CinemaEndpoint.cs @@ -0,0 +1,189 @@ +using api_cinema_challenge.DTO; +using api_cinema_challenge.Models; +using api_cinema_challenge.Repository; +using Microsoft.AspNetCore.Mvc; + +namespace api_cinema_challenge.EndPoint +{ + public static class CinemaEndpoint + { + public static void ConfigureCinemaEndPoint (this WebApplication app) + { + app.MapPost("/customers", AddCustomer); + app.MapGet("/customers", GetCustomers); + app.MapPut("/customers/{id}", UpdateCustomer); + app.MapDelete("/customers/{id}", DeleteCustomer); + + app.MapPost("/movies", AddMovie); + app.MapGet("/movies", GetMovies); + app.MapPut("/movies/{id}", UpdateMovie); + app.MapDelete("movies/{id}", DeleteMovie); + + app.MapPost("/movies/{id}/screenings", AddScreening); + app.MapGet("movies/{id}/screenings", GetScreenings); + + + } + public static async Task AddCustomer(IRepository repo, CustomerPost customer) + { + Customer result = new Customer + { + Name = customer.name, + Email = customer.email, + Phone = customer.phone, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + await repo.AddCustomer(result); + + return TypedResults.Ok(result); + } + public static async Task GetCustomers(IRepository repo) + { + var customers = await repo.GetCustomers(); + + return TypedResults.Ok(customers); + } + public static async Task UpdateCustomer(IRepository repo, CustomerPost customer, int id) + { + var customerReturned = await repo.GetCustomer(id); + if (customerReturned == null) + { + return TypedResults.NotFound(); + } + if (customer.name != null) customerReturned.Name = customer.name; + if (customer.email != null) customerReturned.Email = customer.email; + if (customer.phone != null) customerReturned.Phone = customer.phone; + + var updated = await repo.UpdateCustomer(customerReturned, id); + + return TypedResults.Ok(updated); + } + public static async Task DeleteCustomer(IRepository repo, int id) + { + var deleted = await repo.DeleteCustomer(id); + return TypedResults.Ok(deleted); + } + + public static async Task AddMovie(IRepository repo, MoviePost movie) + { + Movie result = new Movie + { + Title = movie.Title, + Rating = movie.Rating, + Description = movie.Description, + RuntimeMins = movie.RuntimeMins, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + await repo.AddMovie(result); + + return TypedResults.Ok(result); + + } + public static async Task GetMovies(IRepository repo) + { + var movies = await repo.GetMovies(); + + var movieDTO = movies.Select(m => new MovieDTO + { + Title = m.Title, + Rating = m.Rating, + Description = m.Description, + RuntimeMins = m.RuntimeMins, + CreatedAt = m.CreatedAt, + UpdatedAt = m.UpdatedAt, + Screenings = m.Screenings.Select(s => new ScreeningDTO + { + ScreenNumber = s.ScreenNumber, + Capacity = s.Capacity, + StartsAt = s.StartsAt, + MovieId = s.MovieId, + CreatedAt = s.CreatedAt, + UpdatedAt = s.UpdatedAt + }).ToList() + }); + return TypedResults.Ok(movies); + } + public static async Task UpdateMovie(IRepository repo, MoviePost movie, int id) + { + var movieReturned = await repo.GetMovie(id); + if (movieReturned == null) + { + return TypedResults.NotFound(); + } + if (movie.Title != null) movieReturned.Title = movie.Title; + if (movie.Rating != null) movieReturned.Rating = movie.Rating; + if (movie.Description != null) movieReturned.Description = movie.Description; + if (movie.RuntimeMins != null) movieReturned.RuntimeMins = movie.RuntimeMins; + + var updated = await repo.UpdateMovie(movieReturned, id); + + MovieDTO movieDTO = new MovieDTO + { + Title = movieReturned.Title, + Rating = movieReturned.Rating, + Description = movieReturned.Description, + RuntimeMins = movieReturned.RuntimeMins, + CreatedAt = movieReturned.CreatedAt, + UpdatedAt = movieReturned.UpdatedAt, + Screenings = movieReturned.Screenings.Select(s => new ScreeningDTO + { + ScreenNumber = s.ScreenNumber, + Capacity = s.Capacity, + StartsAt = s.StartsAt, + MovieId = s.MovieId, + CreatedAt = s.CreatedAt, + UpdatedAt = s.UpdatedAt + }).ToList() + }; + return TypedResults.Ok(movieDTO); + } + public static async Task DeleteMovie(IRepository repo, int id) + { + var deleted = await repo.DeleteMovie(id); + return TypedResults.Ok(deleted); + } + + public static async Task AddScreening(IRepository repo, ScreeningPost screening, int id) + { + Screening result = new Screening + { + ScreenNumber = screening.ScreenNumber, + Capacity = screening.Capacity, + StartsAt = screening.StartsAt, + MovieId = screening.MovieId, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + await repo.AddScreening(result); + + return TypedResults.Ok(result); + + } + public static async Task GetScreenings(IRepository repo, int movieId) + { + var result = await repo.GetScreenings(movieId); + var screenings = result.Select(s => new ScreeningDTOBig + { + ScreenNumber = s.ScreenNumber, + Capacity = s.Capacity, + StartsAt = s.StartsAt, + MovieId = s.MovieId, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow, + Movie = new MovieSmall + { + Title = s.Movie.Title, + Rating = s.Movie.Rating, + Description = s.Movie.Description, + RuntimeMins = s.Movie.RuntimeMins, + CreatedAt = s.Movie.CreatedAt, + UpdatedAt = s.Movie.UpdatedAt + } + }); + + return TypedResults.Ok(screenings); + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Models/Customer.cs b/api-cinema-challenge/api-cinema-challenge/Models/Customer.cs new file mode 100644 index 00000000..b9767fb4 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Customer.cs @@ -0,0 +1,10 @@ +namespace api_cinema_challenge.Models +{ + public class Customer : Dateing + { + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string Phone { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Models/Dateing.cs b/api-cinema-challenge/api-cinema-challenge/Models/Dateing.cs new file mode 100644 index 00000000..b952ec3b --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Dateing.cs @@ -0,0 +1,8 @@ +namespace api_cinema_challenge.Models +{ + public abstract class Dateing + { + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Models/Movie.cs b/api-cinema-challenge/api-cinema-challenge/Models/Movie.cs new file mode 100644 index 00000000..ee566e6a --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Movie.cs @@ -0,0 +1,12 @@ +namespace api_cinema_challenge.Models +{ + public class Movie : Dateing + { + public int Id { get; set; } + public string Title { get; set; } + public string Rating { get; set; } + public string Description { get; set; } + public int RuntimeMins { get; set; } + public IEnumerable Screenings { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Models/Screening.cs b/api-cinema-challenge/api-cinema-challenge/Models/Screening.cs new file mode 100644 index 00000000..b3710ec4 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Screening.cs @@ -0,0 +1,13 @@ +namespace api_cinema_challenge.Models +{ + public class Screening : Dateing + { + public int Id { get; set; } + public int ScreenNumber { get; set; } + public int Capacity { get; set; } + + public DateTime StartsAt { get; set; } + public int MovieId { get; set; } + public Movie Movie { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Program.cs b/api-cinema-challenge/api-cinema-challenge/Program.cs index e55d9d54..9835c0c8 100644 --- a/api-cinema-challenge/api-cinema-challenge/Program.cs +++ b/api-cinema-challenge/api-cinema-challenge/Program.cs @@ -1,11 +1,15 @@ using api_cinema_challenge.Data; +using api_cinema_challenge.EndPoint; +using api_cinema_challenge.Repository; var builder = WebApplication.CreateBuilder(args); // Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddDbContext(); +builder.Services.AddScoped(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddDbContext(); var app = builder.Build(); @@ -17,4 +21,11 @@ } app.UseHttpsRedirection(); + +app.MapControllers(); + +app.ConfigureCinemaEndPoint(); + +app.SeedCinemaApi(); + app.Run(); diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs new file mode 100644 index 00000000..63a3ae9c --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs @@ -0,0 +1,23 @@ +using api_cinema_challenge.DTO; +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.Repository +{ + public interface IRepository + { + Task AddCustomer(Customer customer); + Task> GetCustomers(); + Task GetCustomer(int id); + Task UpdateCustomer(Customer customer, int id); + Task DeleteCustomer(int id); + + Task AddMovie(Movie movie); + Task> GetMovies(); + Task GetMovie(int id); + Task UpdateMovie(Movie movie, int id); + Task DeleteMovie(int id); + + Task AddScreening(Screening screening); + Task> GetScreenings(int movieId); + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs new file mode 100644 index 00000000..62e44bbc --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs @@ -0,0 +1,115 @@ +using api_cinema_challenge.Data; +using api_cinema_challenge.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query.Internal; + +namespace api_cinema_challenge.Repository +{ + public class Repository : IRepository + { + CinemaContext _db; + public Repository(CinemaContext db) + { + _db = db; + } + + public async Task AddCustomer(Customer customer) + { + await _db.Customers.AddAsync(customer); + await _db.SaveChangesAsync(); + return customer; + } + + public async Task AddMovie(Movie movie) + { + await _db.Movies.AddAsync(movie); + await _db.SaveChangesAsync(); + return movie; + } + + public async Task AddScreening(Screening screening) + { + var movie = await _db.Movies.FirstOrDefaultAsync(x => x.Id == screening.MovieId); + if (movie == null) + { + return null; + } + screening.Movie = movie; + await _db.Screenings.AddAsync(screening); + await _db.SaveChangesAsync(); + return screening; + } + + public async Task DeleteCustomer(int id) + { + var result = await _db.Customers.FindAsync(id); + _db.Customers.Remove(result); + await _db.SaveChangesAsync(); + return result; + } + + public async Task DeleteMovie(int id) + { + var result = await _db.Movies.FindAsync(id); + _db.Movies.Remove(result); + await _db.SaveChangesAsync(); + return result; + } + + public async Task> GetCustomers() + { + return await _db.Customers.ToListAsync(); + } + public async Task GetCustomer(int id) + { + return await _db.Customers.FirstOrDefaultAsync(b => b.Id == id); + } + + public async Task> GetMovies() + { + return await _db.Movies.Include(x => x.Screenings).ToListAsync(); + } + public async Task GetMovie(int id) + { + return await _db.Movies.Include(b => b.Screenings).FirstOrDefaultAsync(b => b.Id == id); + } + + public async Task> GetScreenings(int id) + { + return await _db.Screenings.Include(x => x.Movie).Where(x => x.MovieId == id).ToListAsync(); + } + + public async Task UpdateCustomer(Customer customer, int id) + { + var result = await _db.Customers.FindAsync(id); + if (result == null) + { + return null; + } + customer.Name = result.Name; + customer.Email = result.Email; + customer.Phone = result.Phone; + customer.UpdatedAt = DateTime.UtcNow; + + await _db.SaveChangesAsync(); + return customer; + } + + public async Task UpdateMovie(Movie movie, int id) + { + var result = await _db.Movies.FindAsync(id); + if (result == null) + { + return null; + } + movie.Title = result.Title; + movie.Rating = result.Rating; + movie.Description = result.Description; + movie.RuntimeMins = result.RuntimeMins; + movie.UpdatedAt = DateTime.UtcNow; + + await _db.SaveChangesAsync(); + return movie; + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj index 8c888bf8..89f468ed 100644 --- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj +++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -27,8 +27,4 @@ - - - -