From ea4669e3051fbe9ac0362097ba40c722d1985f66 Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 08:13:06 +0200
Subject: [PATCH 01/28] updated appsettings with my credentials
---
.../api-cinema-challenge/api-cinema-challenge.csproj | 8 ++++++++
.../api-cinema-challenge/appsettings.example.json | 12 ------------
2 files changed, 8 insertions(+), 12 deletions(-)
delete mode 100644 api-cinema-challenge/api-cinema-challenge/appsettings.example.json
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 11e5c66b..ac5bf732 100644
--- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
+++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
@@ -14,6 +14,10 @@
+
+
+
+
@@ -31,4 +35,8 @@
+
+
+
+
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
From a7437be594b289f7adea5dc68ff5bf612eb6685d Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 08:15:53 +0200
Subject: [PATCH 02/28] updated gitignore to ignore migrations
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index cf332414..48f4c7b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -362,9 +362,11 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
+# prevents build directories and sensitive config files (like your database credentials) from being uploaded.
*/**/appsettings.json
*/**/appsettings.Development.json
*/**/bin/Debug
*/**/bin/Release
*/**/obj/Debug
*/**/obj/Release
+*/Migrations
\ No newline at end of file
From 1818439ea0707514425f75ee4e218741bf70f431 Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 08:19:34 +0200
Subject: [PATCH 03/28] installed packages
---
.../api-cinema-challenge/api-cinema-challenge.csproj | 5 +++++
1 file changed, 5 insertions(+)
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 ac5bf732..63d28ee3 100644
--- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
+++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
@@ -21,6 +21,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
all
@@ -28,6 +32,7 @@
+
From 5e1dc8e5cd3d21661f11077f4dd3c199afa8a8c1 Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 08:22:08 +0200
Subject: [PATCH 04/28] updated program.cs
---
.../api-cinema-challenge/Program.cs | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/api-cinema-challenge/api-cinema-challenge/Program.cs b/api-cinema-challenge/api-cinema-challenge/Program.cs
index e55d9d54..afdf327a 100644
--- a/api-cinema-challenge/api-cinema-challenge/Program.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Program.cs
@@ -1,20 +1,32 @@
using api_cinema_challenge.Data;
+using Microsoft.EntityFrameworkCore;
+using Scalar.AspNetCore;
+using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
-builder.Services.AddDbContext();
+builder.Services.AddOpenApi();
+builder.Services.AddDbContext(options => {
+ options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnectionString"));
+ options.LogTo(message => Debug.WriteLine(message));
+});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
- app.UseSwagger();
- app.UseSwaggerUI();
+ app.MapOpenApi();
+ app.UseSwaggerUI(options =>
+ {
+ options.SwaggerEndpoint("/openapi/v1.json", "Demo API");
+ });
+ app.MapScalarApiReference();
}
app.UseHttpsRedirection();
+
app.Run();
From 6e768a1f68e53fe11821fe1721a25b0729aad5b7 Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 08:38:57 +0200
Subject: [PATCH 05/28] added folder to seperate future code
---
.../api-cinema-challenge/Repository/IRepository.cs | 6 ++++++
.../api-cinema-challenge/Repository/Repository.cs | 6 ++++++
.../api-cinema-challenge/api-cinema-challenge.csproj | 3 +++
3 files changed, 15 insertions(+)
create mode 100644 api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
create mode 100644 api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
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..f890e988
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
@@ -0,0 +1,6 @@
+namespace api_cinema_challenge.Repository
+{
+ public interface IRepository
+ {
+ }
+}
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..0428cb98
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
@@ -0,0 +1,6 @@
+namespace api_cinema_challenge.Repository
+{
+ public class Repository
+ {
+ }
+}
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 63d28ee3..24c6d141 100644
--- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
+++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
@@ -38,6 +38,9 @@
+
+
+
From b829dde7cd53646db0e233bcea35476adf284051 Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 08:46:43 +0200
Subject: [PATCH 06/28] implemented a generic repository
---
.../Repository/IRepository.cs | 14 +++++-
.../Repository/Repository.cs | 47 ++++++++++++++++++-
2 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
index f890e988..f03a0a4f 100644
--- a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
@@ -1,6 +1,16 @@
-namespace api_cinema_challenge.Repository
+using System.Linq.Expressions;
+
+namespace api_cinema_challenge.Repository
{
- public interface IRepository
+ public interface IRepository
{
+ Task> GetAll();
+ Task GetById(int id);
+ Task Delete(int id);
+ Task Add(T entity);
+ Task Update(T entity);
+
+ Task> GetWithIncludes(params Expression>[] includes);
+
}
}
diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
index 0428cb98..9d5481c6 100644
--- a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
@@ -1,6 +1,49 @@
-namespace api_cinema_challenge.Repository
+using api_cinema_challenge.Data;
+using Microsoft.EntityFrameworkCore;
+using System.Linq.Expressions;
+
+namespace api_cinema_challenge.Repository
{
- public class Repository
+ public class Repository : IRepository where T : class
{
+ private CinemaContext _db;
+ private DbSet _table = null!;
+
+ public Repository(CinemaContext db)
+ {
+ _db = db;
+ _table = db.Set();
+ }
+
+
+ public async Task Add(T entity)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task Delete(int id)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task> GetAll()
+ {
+ return await _table.ToListAsync();
+ }
+
+ public async Task GetById(int id)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task> GetWithIncludes(params Expression>[] includes)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task Update(T entity)
+ {
+ throw new NotImplementedException();
+ }
}
}
From e5eb5ea21e2cddf6d98099eff34ca6426c383392 Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 09:02:09 +0200
Subject: [PATCH 07/28] impemented customer model and customergetdto, and
seeded 3 customers in onmodelcreating in datacontext
---
.../DTOs/CustomerDTOs/CustomerGetDto.cs | 12 +++++++++
.../Data/CinemaContext.cs | 25 +++++++++--------
.../api-cinema-challenge/Models/Customer.cs | 27 +++++++++++++++++++
.../api-cinema-challenge.csproj | 3 ---
4 files changed, 51 insertions(+), 16 deletions(-)
create mode 100644 api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs
create mode 100644 api-cinema-challenge/api-cinema-challenge/Models/Customer.cs
diff --git a/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs b/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs
new file mode 100644
index 00000000..a03c3e79
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs
@@ -0,0 +1,12 @@
+namespace api_cinema_challenge.DTOs.CustomerDTOs
+{
+ public class CustomerGetDto
+ {
+ public int Id { get; set; }
+ public required string Name { get; set; }
+ public required string Email { get; set; }
+ public required 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/Data/CinemaContext.cs b/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs
index ad4fe854..d03fee2f 100644
--- a/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs
@@ -1,26 +1,25 @@
-using Microsoft.EntityFrameworkCore;
+using api_cinema_challenge.Models;
+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);
- }
+ public CinemaContext(DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ // Seed data for the database
+ modelBuilder.Entity().HasData(
+ new Customer { Id = 1, Name = "Lionel Messi", Email = "messi@messi.messi", Phone = "90121413" },
+ new Customer { Id = 2, Name = "Cristiano Ronaldo", Email = "ronaldo@ronaldo.ronaldo", Phone = "90121414" },
+ new Customer { Id = 3, Name = "Wayne Rooney", Email = "rooney@rooney.rooney", Phone = "90121415" }
+ );
+
}
+
+ public DbSet Customers { get; set; }
}
}
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..d0321092
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Models/Customer.cs
@@ -0,0 +1,27 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace api_cinema_challenge.Models
+{
+ [Table("Customers")]
+ public class Customer
+ {
+ [Key]
+ [Column("CustomerId")]
+ public int Id { get; set; }
+
+ [Required]
+ [Column("CustomerName")]
+ public required string Name { get; set; }
+
+ [Required]
+ [Column("CustomerEmail")]
+ [EmailAddress]
+ public required string Email { get; set; }
+
+ [Required]
+ [Column("CustomerPhone")]
+ [Phone]
+ public required string Phone { get; set; }
+ }
+}
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 24c6d141..a079a4fe 100644
--- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
+++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
@@ -37,10 +37,7 @@
-
-
-
From 53fcf430aa9ed7eeaeb50bd0f5eb054bcf36acef Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 09:10:50 +0200
Subject: [PATCH 08/28] implemented getCustomers endpoint, updated program.cs
to coinfigure endpoints of customers
---
.../Endpoints/CustomerEndpoints.cs | 38 +++++++++
...erModelAndSeededThreeCustomers.Designer.cs | 81 +++++++++++++++++++
...AddCustomerModelAndSeededThreeCustomers.cs | 49 +++++++++++
.../Migrations/CinemaContextModelSnapshot.cs | 78 ++++++++++++++++++
.../api-cinema-challenge/Program.cs | 7 +-
.../api-cinema-challenge.csproj | 4 -
6 files changed, 252 insertions(+), 5 deletions(-)
create mode 100644 api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
create mode 100644 api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.Designer.cs
create mode 100644 api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.cs
create mode 100644 api-cinema-challenge/api-cinema-challenge/Migrations/CinemaContextModelSnapshot.cs
diff --git a/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs b/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
new file mode 100644
index 00000000..8c9b4dd3
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
@@ -0,0 +1,38 @@
+using api_cinema_challenge.DTOs.CustomerDTOs;
+using api_cinema_challenge.Models;
+using api_cinema_challenge.Repository;
+using Microsoft.AspNetCore.Mvc;
+
+namespace api_cinema_challenge.Endpoints
+{
+ public static class CustomerEndpoints
+ {
+ public static void ConfigureCustomerEndpoint(this WebApplication app)
+ {
+ var customers = app.MapGroup("customers");
+
+ customers.MapGet("/", GetCustomers);
+
+ }
+
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public static async Task GetCustomers(IRepository repository)
+ {
+ var customers = await repository.GetAll();
+ if (customers == null || !customers.Any()) { return Results.NotFound("No customers found."); }
+
+ var customerDto = customers.Select(c => new CustomerGetDto
+ {
+ Id = c.Id,
+ Name = c.Name,
+ Email = c.Email,
+ Phone = c.Phone
+ }).ToList();
+
+ return Results.Ok(customerDto);
+
+ }
+
+ }
+}
diff --git a/api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.Designer.cs b/api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.Designer.cs
new file mode 100644
index 00000000..1041da16
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.Designer.cs
@@ -0,0 +1,81 @@
+//
+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(CinemaContext))]
+ [Migration("20250822070238_AddCustomerModelAndSeededThreeCustomers")]
+ partial class AddCustomerModelAndSeededThreeCustomers
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("api_cinema_challenge.Models.Customer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("CustomerId");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("CustomerEmail");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("CustomerName");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("CustomerPhone");
+
+ b.HasKey("Id");
+
+ b.ToTable("Customers");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ Email = "messi@messi.messi",
+ Name = "Lionel Messi",
+ Phone = "90121413"
+ },
+ new
+ {
+ Id = 2,
+ Email = "ronaldo@ronaldo.ronaldo",
+ Name = "Cristiano Ronaldo",
+ Phone = "90121414"
+ },
+ new
+ {
+ Id = 3,
+ Email = "rooney@rooney.rooney",
+ Name = "Wayne Rooney",
+ Phone = "90121415"
+ });
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.cs b/api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.cs
new file mode 100644
index 00000000..c9a84711
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Migrations/20250822070238_AddCustomerModelAndSeededThreeCustomers.cs
@@ -0,0 +1,49 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
+
+namespace api_cinema_challenge.Migrations
+{
+ ///
+ public partial class AddCustomerModelAndSeededThreeCustomers : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Customers",
+ columns: table => new
+ {
+ CustomerId = table.Column(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ CustomerName = table.Column(type: "text", nullable: false),
+ CustomerEmail = table.Column(type: "text", nullable: false),
+ CustomerPhone = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Customers", x => x.CustomerId);
+ });
+
+ migrationBuilder.InsertData(
+ table: "Customers",
+ columns: new[] { "CustomerId", "CustomerEmail", "CustomerName", "CustomerPhone" },
+ values: new object[,]
+ {
+ { 1, "messi@messi.messi", "Lionel Messi", "90121413" },
+ { 2, "ronaldo@ronaldo.ronaldo", "Cristiano Ronaldo", "90121414" },
+ { 3, "rooney@rooney.rooney", "Wayne Rooney", "90121415" }
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Customers");
+ }
+ }
+}
diff --git a/api-cinema-challenge/api-cinema-challenge/Migrations/CinemaContextModelSnapshot.cs b/api-cinema-challenge/api-cinema-challenge/Migrations/CinemaContextModelSnapshot.cs
new file mode 100644
index 00000000..f12b061a
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Migrations/CinemaContextModelSnapshot.cs
@@ -0,0 +1,78 @@
+//
+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(CinemaContext))]
+ partial class CinemaContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("api_cinema_challenge.Models.Customer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("CustomerId");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("CustomerEmail");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("CustomerName");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("CustomerPhone");
+
+ b.HasKey("Id");
+
+ b.ToTable("Customers");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ Email = "messi@messi.messi",
+ Name = "Lionel Messi",
+ Phone = "90121413"
+ },
+ new
+ {
+ Id = 2,
+ Email = "ronaldo@ronaldo.ronaldo",
+ Name = "Cristiano Ronaldo",
+ Phone = "90121414"
+ },
+ new
+ {
+ Id = 3,
+ Email = "rooney@rooney.rooney",
+ Name = "Wayne Rooney",
+ Phone = "90121415"
+ });
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/api-cinema-challenge/api-cinema-challenge/Program.cs b/api-cinema-challenge/api-cinema-challenge/Program.cs
index afdf327a..242000e6 100644
--- a/api-cinema-challenge/api-cinema-challenge/Program.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Program.cs
@@ -1,4 +1,7 @@
using api_cinema_challenge.Data;
+using api_cinema_challenge.Endpoints;
+using api_cinema_challenge.Models;
+using api_cinema_challenge.Repository;
using Microsoft.EntityFrameworkCore;
using Scalar.AspNetCore;
using System.Diagnostics;
@@ -14,6 +17,8 @@
options.LogTo(message => Debug.WriteLine(message));
});
+builder.Services.AddScoped, Repository>();
+
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -28,5 +33,5 @@
}
app.UseHttpsRedirection();
-
+app.ConfigureCustomerEndpoint();
app.Run();
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 a079a4fe..17cbc89b 100644
--- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
+++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
@@ -36,10 +36,6 @@
-
-
-
-
From 6edd21fba94f3b99bdfbe4d27699548bede56d7c Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 09:34:28 +0200
Subject: [PATCH 09/28] updated add endpoint for customer
---
.../{CustomerGetDto.cs => CustomerDto.cs} | 2 +-
.../DTOs/CustomerDTOs/CustomerPostDto.cs | 14 +++++++++
.../Endpoints/CustomerEndpoints.cs | 30 +++++++++++++++++--
.../Repository/IRepository.cs | 1 -
.../Repository/Repository.cs | 9 ++----
.../api-cinema-challenge.csproj | 2 ++
6 files changed, 47 insertions(+), 11 deletions(-)
rename api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/{CustomerGetDto.cs => CustomerDto.cs} (92%)
create mode 100644 api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerPostDto.cs
diff --git a/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs b/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerDto.cs
similarity index 92%
rename from api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs
rename to api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerDto.cs
index a03c3e79..d7277671 100644
--- a/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerGetDto.cs
+++ b/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerDto.cs
@@ -1,6 +1,6 @@
namespace api_cinema_challenge.DTOs.CustomerDTOs
{
- public class CustomerGetDto
+ public class CustomerDto
{
public int Id { get; set; }
public required string Name { get; set; }
diff --git a/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerPostDto.cs b/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerPostDto.cs
new file mode 100644
index 00000000..bc17e795
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/DTOs/CustomerDTOs/CustomerPostDto.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace api_cinema_challenge.DTOs.CustomerDTOs
+{
+ public class CustomerPostDto
+ {
+ public required string Name { get; set; }
+ [EmailAddress]
+ public required string Email { get; set; }
+ [Phone]
+ public required string Phone { get; set; }
+
+ }
+}
diff --git a/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs b/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
index 8c9b4dd3..7e7775bd 100644
--- a/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
@@ -2,6 +2,7 @@
using api_cinema_challenge.Models;
using api_cinema_challenge.Repository;
using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel.DataAnnotations;
namespace api_cinema_challenge.Endpoints
{
@@ -12,7 +13,8 @@ public static void ConfigureCustomerEndpoint(this WebApplication app)
var customers = app.MapGroup("customers");
customers.MapGet("/", GetCustomers);
-
+ customers.MapPost("/", AddCustomer);
+
}
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -22,7 +24,7 @@ public static async Task GetCustomers(IRepository repository)
var customers = await repository.GetAll();
if (customers == null || !customers.Any()) { return Results.NotFound("No customers found."); }
- var customerDto = customers.Select(c => new CustomerGetDto
+ var customerDto = customers.Select(c => new CustomerDto
{
Id = c.Id,
Name = c.Name,
@@ -30,7 +32,29 @@ public static async Task GetCustomers(IRepository repository)
Phone = c.Phone
}).ToList();
- return Results.Ok(customerDto);
+ return TypedResults.Ok(customerDto);
+
+ }
+
+ [ProducesResponseType(StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public static async Task AddCustomer(IRepository repository, [FromBody] CustomerPostDto model, HttpRequest request)
+ {
+ if (model == null) return TypedResults.BadRequest("Invalid customer data");
+ if (string.IsNullOrWhiteSpace(model.Name) || string.IsNullOrWhiteSpace(model.Email)) return TypedResults.BadRequest("Invalid customer data");
+
+ var phoneAttribute = new PhoneAttribute();
+ if (!phoneAttribute.IsValid(model.Phone))
+ return TypedResults.BadRequest("Invalid phone number format.");
+
+ var newCustomer = new Customer { Name = model.Name, Email = model.Email, Phone = model.Phone };
+ var addedCustomer = await repository.Add(newCustomer);
+
+ var customerDto = new CustomerDto { Id = addedCustomer.Id, Name = addedCustomer.Name, Email=addedCustomer.Email, Phone = addedCustomer.Phone };
+
+ var baseUrl = $"{request.Scheme}://{request.Host}{request.PathBase}";
+ var location = $"{baseUrl}/patients/{addedCustomer.Id}";
+ return TypedResults.Created(location, customerDto);
}
diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
index f03a0a4f..d95cbca7 100644
--- a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
@@ -5,7 +5,6 @@ namespace api_cinema_challenge.Repository
public interface IRepository
{
Task> GetAll();
- Task GetById(int id);
Task Delete(int id);
Task Add(T entity);
Task Update(T entity);
diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
index 9d5481c6..988b3438 100644
--- a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
@@ -18,7 +18,9 @@ public Repository(CinemaContext db)
public async Task Add(T entity)
{
- throw new NotImplementedException();
+ await _table.AddAsync(entity);
+ await _db.SaveChangesAsync();
+ return entity;
}
public async Task Delete(int id)
@@ -31,11 +33,6 @@ public async Task> GetAll()
return await _table.ToListAsync();
}
- public async Task GetById(int id)
- {
- throw new NotImplementedException();
- }
-
public async Task> GetWithIncludes(params Expression>[] includes)
{
throw new NotImplementedException();
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 17cbc89b..445d6699 100644
--- a/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
+++ b/api-cinema-challenge/api-cinema-challenge/api-cinema-challenge.csproj
@@ -19,6 +19,8 @@
+
+
From 7d00ae37569e408e547f3f046c420b8abd58f90f Mon Sep 17 00:00:00 2001
From: Mathias Handeland <127216029+MathiasHandeland@users.noreply.github.com>
Date: Fri, 22 Aug 2025 10:31:26 +0200
Subject: [PATCH 10/28] added getcustomerbyid endpoint
---
.../Endpoints/CustomerEndpoints.cs | 47 +++++++++++++++----
.../api-cinema-challenge/Program.cs | 4 ++
.../Repository/IRepository.cs | 3 +-
.../Repository/Repository.cs | 12 ++++-
.../Validators/CustomerPostValidator.cs | 25 ++++++++++
.../Validators/ValidationFilter.cs | 32 +++++++++++++
6 files changed, 111 insertions(+), 12 deletions(-)
create mode 100644 api-cinema-challenge/api-cinema-challenge/Validators/CustomerPostValidator.cs
create mode 100644 api-cinema-challenge/api-cinema-challenge/Validators/ValidationFilter.cs
diff --git a/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs b/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
index 7e7775bd..fcf1c543 100644
--- a/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Endpoints/CustomerEndpoints.cs
@@ -1,6 +1,7 @@
using api_cinema_challenge.DTOs.CustomerDTOs;
using api_cinema_challenge.Models;
using api_cinema_challenge.Repository;
+using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
@@ -12,11 +13,32 @@ public static void ConfigureCustomerEndpoint(this WebApplication app)
{
var customers = app.MapGroup("customers");
+ customers.MapGet("/{id}", GetCustomerById);
customers.MapGet("/", GetCustomers);
customers.MapPost("/", AddCustomer);
+ // customers.MapDelete("/{id}"DeleteCustomer);
}
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public static async Task GetCustomerById(int id, IRepository repository)
+ {
+ var customers = await repository.GetAll();
+ var targetCustomer = customers.FirstOrDefault(c => c.Id == id);
+ if (targetCustomer == null) { return TypedResults.NotFound($"Customer with id {id} not found."); }
+
+ var customerDto = new CustomerDto
+ {
+ Id = targetCustomer.Id,
+ Name = targetCustomer.Name,
+ Email = targetCustomer.Email,
+ Phone = targetCustomer.Phone
+ };
+
+ return TypedResults.Ok(customerDto);
+ }
+
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public static async Task GetCustomers(IRepository repository)
@@ -38,14 +60,16 @@ public static async Task GetCustomers(IRepository repository)
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
- public static async Task AddCustomer(IRepository repository, [FromBody] CustomerPostDto model, HttpRequest request)
+ public static async Task AddCustomer(IRepository repository, [FromBody] CustomerPostDto model, IValidator validator, HttpRequest request)
{
- if (model == null) return TypedResults.BadRequest("Invalid customer data");
- if (string.IsNullOrWhiteSpace(model.Name) || string.IsNullOrWhiteSpace(model.Email)) return TypedResults.BadRequest("Invalid customer data");
-
- var phoneAttribute = new PhoneAttribute();
- if (!phoneAttribute.IsValid(model.Phone))
- return TypedResults.BadRequest("Invalid phone number format.");
+ if (model == null) { return TypedResults.BadRequest("Invalid customer data"); }
+
+ var validationResult = await validator.ValidateAsync(model);
+ if (!validationResult.IsValid)
+ {
+ var errors = string.Join("; ", validationResult.Errors.Select(e => e.ErrorMessage));
+ return TypedResults.BadRequest(errors);
+ }
var newCustomer = new Customer { Name = model.Name, Email = model.Email, Phone = model.Phone };
var addedCustomer = await repository.Add(newCustomer);
@@ -53,10 +77,15 @@ public static async Task AddCustomer(IRepository repository,
var customerDto = new CustomerDto { Id = addedCustomer.Id, Name = addedCustomer.Name, Email=addedCustomer.Email, Phone = addedCustomer.Phone };
var baseUrl = $"{request.Scheme}://{request.Host}{request.PathBase}";
- var location = $"{baseUrl}/patients/{addedCustomer.Id}";
+ var location = $"{baseUrl}/customers/{addedCustomer.Id}";
return TypedResults.Created(location, customerDto);
-
+
}
+ public static async Task DeleteCustomer(int id, IRepository repository)
+ {
+ throw new NotImplementedException();
+
+ }
}
}
diff --git a/api-cinema-challenge/api-cinema-challenge/Program.cs b/api-cinema-challenge/api-cinema-challenge/Program.cs
index 242000e6..584393a4 100644
--- a/api-cinema-challenge/api-cinema-challenge/Program.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Program.cs
@@ -1,7 +1,10 @@
using api_cinema_challenge.Data;
+using api_cinema_challenge.DTOs.CustomerDTOs;
using api_cinema_challenge.Endpoints;
using api_cinema_challenge.Models;
using api_cinema_challenge.Repository;
+using api_cinema_challenge.Validators;
+using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Scalar.AspNetCore;
using System.Diagnostics;
@@ -18,6 +21,7 @@
});
builder.Services.AddScoped, Repository>();
+builder.Services.AddValidatorsFromAssemblyContaining(typeof(CustomerPostValidator));
var app = builder.Build();
diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
index d95cbca7..88fba549 100644
--- a/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/IRepository.cs
@@ -5,7 +5,8 @@ namespace api_cinema_challenge.Repository
public interface IRepository
{
Task> GetAll();
- Task Delete(int id);
+ Task GetById(int id);
+ Task Delete(object id);
Task Add(T entity);
Task Update(T entity);
diff --git a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
index 988b3438..952f63e0 100644
--- a/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
+++ b/api-cinema-challenge/api-cinema-challenge/Repository/Repository.cs
@@ -23,9 +23,12 @@ public async Task Add(T entity)
return entity;
}
- public async Task Delete(int id)
+ public async Task Delete(object id)
{
- throw new NotImplementedException();
+ T entity = await _table.FindAsync(id);
+ _table.Remove(entity);
+ await _db.SaveChangesAsync();
+ return entity;
}
public async Task> GetAll()
@@ -33,6 +36,11 @@ public async Task> GetAll()
return await _table.ToListAsync();
}
+ public async Task GetById(int id)
+ {
+ return await _table.FindAsync(id);
+ }
+
public async Task> GetWithIncludes(params Expression>[] includes)
{
throw new NotImplementedException();
diff --git a/api-cinema-challenge/api-cinema-challenge/Validators/CustomerPostValidator.cs b/api-cinema-challenge/api-cinema-challenge/Validators/CustomerPostValidator.cs
new file mode 100644
index 00000000..b4096913
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Validators/CustomerPostValidator.cs
@@ -0,0 +1,25 @@
+using api_cinema_challenge.DTOs.CustomerDTOs;
+using FluentValidation;
+
+namespace api_cinema_challenge.Validators
+{
+ public class CustomerPostValidator : AbstractValidator
+ {
+ public CustomerPostValidator()
+ {
+ RuleFor(x => x.Name)
+ .NotEmpty().WithMessage("Name is required.")
+ .Must(name => !string.IsNullOrWhiteSpace(name)).WithMessage("Name cannot be whitespace.");
+
+ RuleFor(x => x.Email)
+ .NotEmpty().WithMessage("Email is required.")
+ .EmailAddress().WithMessage("Email must be a valid email address.");
+
+ RuleFor(x => x.Phone)
+ .NotEmpty().WithMessage("Phone is required.")
+ .Matches(@"^\+?\d{7,15}$").WithMessage("Phone must be a valid phone number.");
+
+ }
+
+ }
+}
diff --git a/api-cinema-challenge/api-cinema-challenge/Validators/ValidationFilter.cs b/api-cinema-challenge/api-cinema-challenge/Validators/ValidationFilter.cs
new file mode 100644
index 00000000..9359cacc
--- /dev/null
+++ b/api-cinema-challenge/api-cinema-challenge/Validators/ValidationFilter.cs
@@ -0,0 +1,32 @@
+using FluentValidation;
+
+namespace api_cinema_challenge.Validators
+{
+ public class ValidationFilter : IEndpointFilter
+ {
+ public async ValueTask