diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CreateCustomerDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CreateCustomerDTO.cs new file mode 100644 index 00000000..c7eff559 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CreateCustomerDTO.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace api_cinema_challenge.DTO.CustomerDTO +{ + public class CreateCustomerDTO + { + 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/CustomerDTO/CustomerNoTicketDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CustomerNoTicketDTO.cs new file mode 100644 index 00000000..582475aa --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CustomerNoTicketDTO.cs @@ -0,0 +1,19 @@ +using api_cinema_challenge.DTO.TicketDTO; + +namespace api_cinema_challenge.DTO.CustomerDTO +{ + public class CustomerNoTicketDTO + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Email { get; set; } + + public string Phone { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CustomersDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CustomersDTO.cs new file mode 100644 index 00000000..42a7e2dd --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/CustomerDTO/CustomersDTO.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations.Schema; +using api_cinema_challenge.DTO.TicketDTO; +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.DTO.CustomerDTO +{ + public class CustomersDTO + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Email { get; set; } + + public string Phone { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public virtual ICollection Tickets { get; set; } = new List(); + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO/CreateMovieDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO/CreateMovieDTO.cs new file mode 100644 index 00000000..4ec0bd6f --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO/CreateMovieDTO.cs @@ -0,0 +1,14 @@ +namespace api_cinema_challenge.DTO.MovieDTO +{ + public class CreateMovieDTO + { + 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/MovieDTO/MoviesDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO/MoviesDTO.cs new file mode 100644 index 00000000..1f07b2f3 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/MovieDTO/MoviesDTO.cs @@ -0,0 +1,23 @@ +using api_cinema_challenge.DTO.ScreeningDTO; +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.DTO.MovieDTO +{ + public class MoviesDTO + { + 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 DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + public virtual ICollection Screenings { get; set; } = new List(); + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/CreateScreeningDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/CreateScreeningDTO.cs new file mode 100644 index 00000000..07057042 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/CreateScreeningDTO.cs @@ -0,0 +1,14 @@ +namespace api_cinema_challenge.DTO.ScreeningDTO +{ + public class CreateScreeningDTO + { + + 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/DTO/ScreeningDTO/ScreeningNoMovieDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/ScreeningNoMovieDTO.cs new file mode 100644 index 00000000..d919b83c --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/ScreeningNoMovieDTO.cs @@ -0,0 +1,15 @@ +using api_cinema_challenge.DTO.MovieDTO; + +namespace api_cinema_challenge.DTO.ScreeningDTO +{ + public class ScreeningNoMovieDTO + { + public int Id { get; set; } + + public int ScreenNumber { get; set; } + + public int Capacity { get; set; } + + public DateTime StartsAt { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/ScreeningsDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/ScreeningsDTO.cs new file mode 100644 index 00000000..5d6fa32e --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/ScreeningDTO/ScreeningsDTO.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; +using api_cinema_challenge.DTO.MovieDTO; + +namespace api_cinema_challenge.DTO.ScreeningDTO +{ + public class ScreeningsDTO + { + public int Id { get; set; } + + public int ScreenNumber { get; set; } + + public int Capacity { get; set; } + + public DateTime StartsAt { get; set; } + public MoviesDTO Movie { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/TicketDTO/CreateTicketDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/TicketDTO/CreateTicketDTO.cs new file mode 100644 index 00000000..3205bd84 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/TicketDTO/CreateTicketDTO.cs @@ -0,0 +1,13 @@ +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.DTO.TicketDTO +{ + public class CreateTicketDTO + { + public int NumSeats { get; set; } + + public int CustomerId { get; set; } + + public int ScreeningId { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/DTO/TicketDTO/TicketsDTO.cs b/api-cinema-challenge/api-cinema-challenge/DTO/TicketDTO/TicketsDTO.cs new file mode 100644 index 00000000..d0ed9fa1 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/DTO/TicketDTO/TicketsDTO.cs @@ -0,0 +1,22 @@ +using api_cinema_challenge.DTO.CustomerDTO; +using api_cinema_challenge.DTO.ScreeningDTO; +using api_cinema_challenge.Models; +using System.ComponentModel.DataAnnotations.Schema; + +namespace api_cinema_challenge.DTO.TicketDTO +{ + public class TicketsDTO + { + public int Id { get; set; } + + public int NumSeats { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public CustomerNoTicketDTO Customer { get; set; } + + public ScreeningsDTO Screening { get; set; } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs b/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs deleted file mode 100644 index ad4fe854..00000000 --- a/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json.Linq; - -namespace api_cinema_challenge.Data -{ - public class CinemaContext : DbContext - { - private string _connectionString; - public CinemaContext(DbContextOptions options) : base(options) - { - var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - _connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString")!; - this.Database.EnsureCreated(); - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseNpgsql(_connectionString); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - - } - } -} diff --git a/api-cinema-challenge/api-cinema-challenge/Data/DataContext.cs b/api-cinema-challenge/api-cinema-challenge/Data/DataContext.cs new file mode 100644 index 00000000..4f1cabe9 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Data/DataContext.cs @@ -0,0 +1,30 @@ +using api_cinema_challenge.Models; +using Microsoft.EntityFrameworkCore; + +namespace api_cinema_challenge.Data +{ + public class DataContext : DbContext + { + private string connectionString; + public DataContext() + { + var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString"); + + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql(connectionString); + + //set primary of order? + + //seed data? + + } + public DbSet Movies { get; set; } + public DbSet Customers { get; set; } + public DbSet Tickets { get; set; } + public DbSet Screenings { get; set; } + + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Data/Seed.cs b/api-cinema-challenge/api-cinema-challenge/Data/Seed.cs new file mode 100644 index 00000000..3c8a7f88 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Data/Seed.cs @@ -0,0 +1,73 @@ +using api_cinema_challenge.Models; + +namespace api_cinema_challenge.Data +{ + public static class Seed + { + private static readonly Random Random = new Random(); + + public async static Task SeedDatabase(this WebApplication app) + { + using (var db = new DataContext()) + { + // Seed Movies + if (!db.Movies.Any()) + { + var movies = new List + { + new Movie { Title = "Inception", Rating = "PG-13", Description = "A mind-bending thriller", RuntimeMins = 148, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new Movie { Title = "The Matrix", Rating = "R", Description = "A sci-fi classic", RuntimeMins = 136, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } + }; + db.Add(movies); + await db.SaveChangesAsync(); + } + + // Seed Customers + if (!db.Customers.Any()) + { + var customers = new List + { + new Customer { Name = "John Doe", Email = "john@example.com", Phone = "123-456-7890", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new Customer { Name = "Jane Smith", Email = "jane@example.com", Phone = "987-654-3210", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } + }; + db.Add(customers); + await db.SaveChangesAsync(); + } + + // Ensure movies exist before adding screenings + var movie1 = db.Movies.FirstOrDefault(m => m.Title == "Inception"); + var movie2 = db.Movies.FirstOrDefault(m => m.Title == "The Matrix"); + + // Seed Screenings + if (!db.Screenings.Any() && movie1 != null && movie2 != null) + { + var screenings = new List + { + new Screening { ScreenNumber = 5, Capacity = 100, StartsAt = DateTime.UtcNow.AddDays(1), MovieId = movie1.Id, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new Screening { ScreenNumber = 3, Capacity = 80, StartsAt = DateTime.UtcNow.AddDays(2), MovieId = movie2.Id, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } + }; + db.Add(screenings); + await db.SaveChangesAsync(); + } + + // Ensure customers and screenings exist before adding tickets + var customer1 = db.Customers.FirstOrDefault(c => c.Name == "John Doe"); + var customer2 = db.Customers.FirstOrDefault(c => c.Name == "Jane Smith"); + var screening1 = db.Screenings.FirstOrDefault(s => s.ScreenNumber == 5); + var screening2 = db.Screenings.FirstOrDefault(s => s.ScreenNumber == 3); + + // Seed Tickets + if (!db.Tickets.Any() && customer1 != null && customer2 != null && screening1 != null && screening2 != null) + { + var tickets = new List + { + new Ticket { NumSeats = 2, CustomerId = customer1.Id, ScreeningId = screening1.Id, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new Ticket { NumSeats = 3, CustomerId = customer2.Id, ScreeningId = screening2.Id, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } + }; + db.Add(tickets); + await db.SaveChangesAsync(); + } + } + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Endpoints/CinemaApi.cs b/api-cinema-challenge/api-cinema-challenge/Endpoints/CinemaApi.cs new file mode 100644 index 00000000..f212f200 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Endpoints/CinemaApi.cs @@ -0,0 +1,204 @@ +using api_cinema_challenge.DTO.CustomerDTO; +using api_cinema_challenge.DTO.MovieDTO; +using api_cinema_challenge.DTO.ScreeningDTO; +using api_cinema_challenge.DTO.TicketDTO; +using api_cinema_challenge.Models; +using api_cinema_challenge.Repository; +using api_cinema_challenge; +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using workshop.wwwapi.Repository; + +namespace exercise.cinemaapi.EndPoints +{ + public static class CinemaApi + { + public static void ConfigureCinemaApi(this WebApplication app) + { + var cinema = app.MapGroup("cinema"); + + cinema.MapGet("/customers", GetCustomers); + cinema.MapGet("/movies", GetMovies); + cinema.MapGet("/movies/{id}/screenings", GetScreenings); + cinema.MapGet("/tickets", GetTickets); + cinema.MapGet("/tickets/{id}", GetTicketById); + + cinema.MapPost("/customers", AddCustomer); + cinema.MapPost("/movies", AddMovie); + cinema.MapPost("/movies/{id}/screenings", AddScreening); + cinema.MapPost("/tickets", AddTicket); + + cinema.MapPut("/customers/{id}", UpdateCustomer); + cinema.MapPut("/movies/{id}", UpdateMovie); + cinema.MapPut("/tickets/{id}", UpdateTicket); + + cinema.MapDelete("/customers/{id}", DeleteCustomer); + cinema.MapDelete("/movies/{id}", DeleteMovie); + cinema.MapDelete("/tickets/{id}", DeleteTicket); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetCustomers(IRepository repository, IMapper mapper) + { + var customers = await repository.GetWithNestedIncludes(query => + query.Include(p => p.Tickets) + .ThenInclude(a => a.Screening)); + var response = mapper.Map>(customers); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetMovies(IRepository repository, IMapper mapper) + { + var movies = await repository.GetWithNestedIncludes(query => + query.Include(p => p.Screenings)); + var response = mapper.Map>(movies); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetScreenings(IRepository repository, IMapper mapper, int id) + { + var screenings = await repository.GetWithNestedIncludes(query => + query.Where(s => s.MovieId == id)); + + var response = mapper.Map>(screenings); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetTickets(IRepository repository, IMapper mapper) + { + var tickets = await repository.GetWithNestedIncludes(query => + query.Include(t => t.Customer) + .Include(t => t.Screening)); + var response = mapper.Map>(tickets); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetTicketById(IRepository repository, IMapper mapper, int id) + { + var ticket = await repository.GetWithNestedIncludes(query => + query.Where(t => t.Id == id) + .Include(t => t.Customer) + .Include(t => t.Screening)); + if (ticket == null) return TypedResults.NotFound(); + + var response = mapper.Map(ticket.FirstOrDefault()); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddCustomer(IRepository repository, IMapper mapper, CreateCustomerDTO customerDto) + { + var customer = mapper.Map(customerDto); + await repository.Add(customer); + return TypedResults.Created($"/cinema/customers/{customer.Id}", customer); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddMovie(IRepository repository, IMapper mapper, CreateMovieDTO movieDto) + { + var movie = mapper.Map(movieDto); + await repository.Add(movie); + return TypedResults.Created($"/cinema/movies/{movie.Id}", movie); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddScreening(IRepository repository, IMapper mapper, int id, CreateScreeningDTO screeningDto) + { + var screening = mapper.Map(screeningDto); + screening.MovieId = id; + await repository.Add(screening); + return TypedResults.Created($"/cinema/movies/{id}/screenings/{screening.Id}", screening); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddTicket(IRepository repository, IRepository customerRepo, IRepository screeningRepo, IMapper mapper, int customerId, int screeningId, CreateTicketDTO ticketDto) + { + var customer = await customerRepo.GetById(customerId); + var screening = await screeningRepo.GetById(screeningId); + + if (customer == null || screening == null) return TypedResults.BadRequest("Invalid customer or screening ID"); + + //var ticket = new Ticket { CustomerId = customerId, ScreeningId = screeningId, Customer = customer, Screening = screening }; + var tickets = new Ticket + { + CustomerId = customerId, + Customer = customer, + ScreeningId = screeningId, + Screening = screening + }; + //var ticket = mapper.Map(tickets); + await repository.Add(tickets); + var saveTicket = await repository.GetById(tickets.Id); + var response = mapper.Map(saveTicket); + return TypedResults.Created($"/cinema/tickets/{saveTicket.Id}", response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task UpdateCustomer(IRepository repository, IMapper mapper, int id, CreateCustomerDTO customerDto) + { + var existingCustomer = await repository.GetById(id); + if (existingCustomer == null) return TypedResults.NotFound(); + + mapper.Map(customerDto, existingCustomer); + await repository.Update(existingCustomer); + return TypedResults.Ok(existingCustomer); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task UpdateMovie(IRepository repository, IMapper mapper, int id, CreateMovieDTO movieDto) + { + var existingMovie = await repository.GetById(id); + if (existingMovie == null) return TypedResults.NotFound(); + + mapper.Map(movieDto, existingMovie); + await repository.Update(existingMovie); + return TypedResults.Ok(existingMovie); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task UpdateTicket(IRepository repository, IMapper mapper, int id, CreateTicketDTO ticketDto) + { + var existingTicket = await repository.GetById(id); + if (existingTicket == null) return TypedResults.NotFound(); + + mapper.Map(ticketDto, existingTicket); + await repository.Update(existingTicket); + return TypedResults.Ok(existingTicket); + } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + public static async Task DeleteCustomer(IRepository repository, int id) + { + var existingCustomer = await repository.GetById(id); + if (existingCustomer == null) return TypedResults.NotFound(); + + await repository.Delete(existingCustomer); + return TypedResults.NoContent(); + } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + public static async Task DeleteMovie(IRepository repository, int id) + { + var existingMovie = await repository.GetById(id); + if (existingMovie == null) return TypedResults.NotFound(); + + await repository.Delete(existingMovie); + return TypedResults.NoContent(); + } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + public static async Task DeleteTicket(IRepository repository, int id) + { + var existingTicket = await repository.GetById(id); + if (existingTicket == null) return TypedResults.NotFound(); + + await repository.Delete(existingTicket); + return TypedResults.NoContent(); + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Migrations/20250202233415_InitialMigration.Designer.cs b/api-cinema-challenge/api-cinema-challenge/Migrations/20250202233415_InitialMigration.Designer.cs new file mode 100644 index 00000000..4d02baae --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Migrations/20250202233415_InitialMigration.Designer.cs @@ -0,0 +1,224 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using api_cinema_challenge.Data; + +#nullable disable + +namespace api_cinema_challenge.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250202233415_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("api_cinema_challenge.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.ToTable("customers"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Screening", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Capacity") + .HasColumnType("integer") + .HasColumnName("capacity"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("MovieId") + .HasColumnType("integer"); + + b.Property("ScreenNumber") + .HasColumnType("integer") + .HasColumnName("screenNumber"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("startsAt"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.ToTable("screenings"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("NumSeats") + .HasColumnType("integer") + .HasColumnName("numSeats"); + + b.Property("ScreeningId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ScreeningId"); + + b.ToTable("tickets"); + }); + + modelBuilder.Entity("api_cinema_challenge.Movie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Rating") + .IsRequired() + .HasColumnType("text") + .HasColumnName("rating"); + + b.Property("RuntimeMins") + .HasColumnType("integer") + .HasColumnName("runtimeMins"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.ToTable("movies"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Screening", b => + { + b.HasOne("api_cinema_challenge.Movie", "Movie") + .WithMany("Screenings") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Ticket", b => + { + b.HasOne("api_cinema_challenge.Customer", "Customer") + .WithMany("Tickets") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("api_cinema_challenge.Models.Screening", "Screening") + .WithMany("Tickets") + .HasForeignKey("ScreeningId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Screening"); + }); + + modelBuilder.Entity("api_cinema_challenge.Customer", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Screening", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("api_cinema_challenge.Movie", b => + { + b.Navigation("Screenings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Migrations/20250202233415_InitialMigration.cs b/api-cinema-challenge/api-cinema-challenge/Migrations/20250202233415_InitialMigration.cs new file mode 100644 index 00000000..6c7b8a7a --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Migrations/20250202233415_InitialMigration.cs @@ -0,0 +1,135 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace api_cinema_challenge.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "customers", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: false), + email = table.Column(type: "text", nullable: false), + phone = table.Column(type: "text", nullable: false), + createdAt = table.Column(type: "timestamp with time zone", nullable: false), + updatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_customers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "movies", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + title = table.Column(type: "text", nullable: false), + rating = table.Column(type: "text", nullable: false), + description = table.Column(type: "text", nullable: false), + runtimeMins = table.Column(type: "integer", nullable: false), + createdAt = table.Column(type: "timestamp with time zone", nullable: false), + updatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_movies", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "screenings", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + screenNumber = table.Column(type: "integer", nullable: false), + capacity = table.Column(type: "integer", nullable: false), + startsAt = table.Column(type: "timestamp with time zone", nullable: false), + createdAt = table.Column(type: "timestamp with time zone", nullable: false), + updatedAt = table.Column(type: "timestamp with time zone", nullable: false), + MovieId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_screenings", x => x.Id); + table.ForeignKey( + name: "FK_screenings_movies_MovieId", + column: x => x.MovieId, + principalTable: "movies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "tickets", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + numSeats = table.Column(type: "integer", nullable: false), + createdAt = table.Column(type: "timestamp with time zone", nullable: false), + updatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CustomerId = table.Column(type: "integer", nullable: false), + ScreeningId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_tickets", x => x.Id); + table.ForeignKey( + name: "FK_tickets_customers_CustomerId", + column: x => x.CustomerId, + principalTable: "customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_tickets_screenings_ScreeningId", + column: x => x.ScreeningId, + principalTable: "screenings", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_screenings_MovieId", + table: "screenings", + column: "MovieId"); + + migrationBuilder.CreateIndex( + name: "IX_tickets_CustomerId", + table: "tickets", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_tickets_ScreeningId", + table: "tickets", + column: "ScreeningId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "tickets"); + + migrationBuilder.DropTable( + name: "customers"); + + migrationBuilder.DropTable( + name: "screenings"); + + migrationBuilder.DropTable( + name: "movies"); + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Migrations/DataContextModelSnapshot.cs b/api-cinema-challenge/api-cinema-challenge/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 00000000..292a834f --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,221 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using api_cinema_challenge.Data; + +#nullable disable + +namespace api_cinema_challenge.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("api_cinema_challenge.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.ToTable("customers"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Screening", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Capacity") + .HasColumnType("integer") + .HasColumnName("capacity"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("MovieId") + .HasColumnType("integer"); + + b.Property("ScreenNumber") + .HasColumnType("integer") + .HasColumnName("screenNumber"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("startsAt"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.ToTable("screenings"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("NumSeats") + .HasColumnType("integer") + .HasColumnName("numSeats"); + + b.Property("ScreeningId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ScreeningId"); + + b.ToTable("tickets"); + }); + + modelBuilder.Entity("api_cinema_challenge.Movie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Rating") + .IsRequired() + .HasColumnType("text") + .HasColumnName("rating"); + + b.Property("RuntimeMins") + .HasColumnType("integer") + .HasColumnName("runtimeMins"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.HasKey("Id"); + + b.ToTable("movies"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Screening", b => + { + b.HasOne("api_cinema_challenge.Movie", "Movie") + .WithMany("Screenings") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Ticket", b => + { + b.HasOne("api_cinema_challenge.Customer", "Customer") + .WithMany("Tickets") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("api_cinema_challenge.Models.Screening", "Screening") + .WithMany("Tickets") + .HasForeignKey("ScreeningId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Screening"); + }); + + modelBuilder.Entity("api_cinema_challenge.Customer", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("api_cinema_challenge.Models.Screening", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("api_cinema_challenge.Movie", b => + { + b.Navigation("Screenings"); + }); +#pragma warning restore 612, 618 + } + } +} 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..036425b6 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Customer.cs @@ -0,0 +1,50 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using api_cinema_challenge.Models; + +namespace api_cinema_challenge +{ + [Table("customers")] + public class Customer + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Column("name")] + public string Name { get; set; } + + [Column("email")] + public string Email { get; set; } + + [Column("phone")] + public string Phone { get; set; } + + [Column("createdAt")] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + [Column("updatedAt")] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + public virtual ICollection Tickets { get; set; } = new List(); + public Customer() { } + + public Customer(int id) + { + Id = id; + } + + public Customer(string name, string email, string phone) + { + Name = name; + Email = email; + Phone = phone; + CreatedAt = DateTime.UtcNow; + UpdatedAt = DateTime.UtcNow; + } + + public void SetUpdatedAt() + { + UpdatedAt = DateTime.UtcNow; + } + } +} 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..4861d31e --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Movie.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using api_cinema_challenge.Models; + +namespace api_cinema_challenge +{ + [Table("movies")] + public class Movie + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Column("title")] + public string Title { get; set; } + + [Column("rating")] + public string Rating { get; set; } + + [Column("description")] + public string Description { get; set; } + + [Column("runtimeMins")] + public int RuntimeMins { get; set; } + + [Column("createdAt")] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + [Column("updatedAt")] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public virtual ICollection Screenings { get; set; } = new List(); + + public void OnCreate() + { + CreatedAt = DateTime.UtcNow; + } + + public void OnUpdate() + { + UpdatedAt = DateTime.UtcNow; + } + } +} 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..91acf485 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Screening.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace api_cinema_challenge.Models +{ + [Table("screenings")] + public class Screening + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Column("screenNumber")] + public int ScreenNumber { get; set; } + + [Column("capacity")] + public int Capacity { get; set; } + + [Column("startsAt")] + public DateTime StartsAt { get; set; } + + [Column("createdAt")] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + [Column("updatedAt")] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + [ForeignKey("MovieId")] + public int MovieId { get; set; } + public virtual Movie Movie { get; set; } + + public virtual ICollection Tickets { get; set; } = new List(); + + public void OnCreate() + { + CreatedAt = DateTime.UtcNow; + } + + public void OnUpdate() + { + UpdatedAt = DateTime.UtcNow; + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Models/Ticket.cs b/api-cinema-challenge/api-cinema-challenge/Models/Ticket.cs new file mode 100644 index 00000000..449a9dac --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Models/Ticket.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace api_cinema_challenge.Models +{ + [Table("tickets")] + public class Ticket + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Column("numSeats")] + public int NumSeats { get; set; } + + [Column("createdAt")] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + [Column("updatedAt")] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + [ForeignKey("CustomerId")] + public int CustomerId { get; set; } + public virtual Customer Customer { get; set; } + + [ForeignKey("ScreeningId")] + public int ScreeningId { get; set; } + public virtual Screening Screening { get; set; } + + public void OnCreate() + { + CreatedAt = DateTime.UtcNow; + } + + public void OnUpdate() + { + UpdatedAt = DateTime.UtcNow; + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Program.cs b/api-cinema-challenge/api-cinema-challenge/Program.cs index e55d9d54..28d71f02 100644 --- a/api-cinema-challenge/api-cinema-challenge/Program.cs +++ b/api-cinema-challenge/api-cinema-challenge/Program.cs @@ -1,11 +1,23 @@ +using api_cinema_challenge; using api_cinema_challenge.Data; +using api_cinema_challenge.Models; +using api_cinema_challenge.Repository; +using exercise.cinemaapi.EndPoints; +using workshop.wwwapi.Repository; var builder = WebApplication.CreateBuilder(args); // Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddDbContext(); +builder.Services.AddAutoMapper(typeof(Program)); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddDbContext(); var app = builder.Build(); @@ -17,4 +29,13 @@ } app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.ConfigureCinemaApi(); + +app.SeedDatabase(); + 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..dae0d468 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs @@ -0,0 +1,22 @@ +using System.Linq.Expressions; + +namespace api_cinema_challenge.Repository +{ + public interface IRepository + { + Task> Get(); + Task GetById(int id); + + Task Add(T entity); + Task Update(T entity); + Task Delete(object id); + Task Save(); + Task> GetWithIncludes(params Expression>[] includes); + Task GetByIdWithIncludes(int id, params Expression>[] includes); + Task> GetWithNestedIncludes(params Func, IQueryable>[] includeActions); + + IQueryable GetQuery(); + + + } +} 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..735489e6 --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs @@ -0,0 +1,96 @@ +using System.Linq.Expressions; +using api_cinema_challenge.Data; +using api_cinema_challenge.Repository; +using Microsoft.EntityFrameworkCore; + + +namespace workshop.wwwapi.Repository +{ + public class Repository : IRepository where T : class + { + private DataContext _db; + private DbSet _table = null!; + public Repository(DataContext db) + { + _db = db; + _table = _db.Set(); + } + + public async Task Add(T entity) + { + _table.Add(entity); + await _db.SaveChangesAsync(); + return entity; + } + + public async Task Delete(object id) + { + T existing = await _table.FindAsync(id); + _table.Remove(existing); + await _db.SaveChangesAsync(); + return existing; + } + + public async Task> Get() + { + return await _table.ToListAsync(); + } + + public async Task GetById(int id) + { + return await _table.FindAsync(id); + } + + public async Task> GetWithIncludes(params Expression>[] includes) + { + IQueryable query = _table; + foreach (var include in includes) + { + query = query.Include(include); + } + return await query.ToListAsync(); + } + + public async Task> GetWithNestedIncludes(params Func, IQueryable>[] includeActions) + { + IQueryable query = _table; + + foreach (var includeAction in includeActions) + { + query = includeAction(query); + } + + return await query.ToListAsync(); + } + + public async Task GetByIdWithIncludes(int id, params Expression>[] includes) + { + IQueryable query = _table; + + foreach (var include in includes) + { + query = query.Include(include); + } + + return await query.FirstOrDefaultAsync(entity => EF.Property(entity, "Id") == id); + } + + public async Task Save() + { + await _db.SaveChangesAsync(); + } + + public async Task Update(T entity) + { + _table.Attach(entity); + _db.Entry(entity).State = EntityState.Modified; + await _db.SaveChangesAsync(); + return entity; + } + + public IQueryable GetQuery() + { + return _table; + } + } +} diff --git a/api-cinema-challenge/api-cinema-challenge/Tools/Mapper.cs b/api-cinema-challenge/api-cinema-challenge/Tools/Mapper.cs new file mode 100644 index 00000000..d6da02de --- /dev/null +++ b/api-cinema-challenge/api-cinema-challenge/Tools/Mapper.cs @@ -0,0 +1,39 @@ +using api_cinema_challenge.DTO.CustomerDTO; +using api_cinema_challenge.DTO.MovieDTO; +using api_cinema_challenge.DTO.ScreeningDTO; +using api_cinema_challenge.DTO.TicketDTO; +using api_cinema_challenge.Models; +using api_cinema_challenge; +using AutoMapper; + +namespace exercise.cinemaapi.Tools +{ + public class Mapper : Profile + { + public Mapper() + { + CreateMap() + .ForMember(dest => dest.Tickets, opt => opt.MapFrom(src => src.Tickets)) + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.Screenings, opt => opt.MapFrom(src => src.Screenings)) + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.Movie, opt => opt.MapFrom(src => src.Movie)) + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer)) + .ForMember(dest => dest.Screening, opt => opt.MapFrom(src => src.Screening)) + .ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } + } +} 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..6ae1fd71 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 @@ -6,29 +6,21 @@ enable api_cinema_challenge - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/api-cinema-challenge/api-cinema-challenge/appsettings.example.json b/api-cinema-challenge/api-cinema-challenge/appsettings.example.json deleted file mode 100644 index b9175fe6..00000000 --- a/api-cinema-challenge/api-cinema-challenge/appsettings.example.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnectionString": "Host=HOST; Database=DATABASE; Username=USERNAME; Password=PASSWORD;" - } -} \ No newline at end of file