From dbcb952aecd8182d40364e0ecdbf462e040cfaf5 Mon Sep 17 00:00:00 2001 From: Rached Mejri Date: Sat, 5 Sep 2026 16:38:53 +0200 Subject: [PATCH 1/3] Access to database tables --- API/Controller/ProductController.cs | 12 +- API/Models/AppDbContext.cs | 6 +- API/Program.cs | 1 + README.md | 1 + docker-compose.yml | 12 ++ init.sql | 275 ++++++++++++++++++++++++++++ 6 files changed, 300 insertions(+), 7 deletions(-) create mode 100644 docker-compose.yml create mode 100644 init.sql diff --git a/API/Controller/ProductController.cs b/API/Controller/ProductController.cs index aad6da4..b2321fa 100644 --- a/API/Controller/ProductController.cs +++ b/API/Controller/ProductController.cs @@ -5,20 +5,24 @@ namespace API.Controller; [ApiController] [Route("api/product")] -public class ProductController : ControllerBase +public class ProductController(AppDbContext context) : ControllerBase { // GET api/product [HttpGet] - public ActionResult GetProduct(int id) + public ActionResult GetProduct( int id) { - return Ok(); + var products = context.Products.ToList(); + return Ok(products); } // POST api/product [HttpPost] public ActionResult CreateProduct([FromBody] Product product) { - return Ok(); + context.Products.Add(product); + context.SaveChanges(); + + return Ok(product); } // PATCH api/product diff --git a/API/Models/AppDbContext.cs b/API/Models/AppDbContext.cs index e581d6f..4342a09 100644 --- a/API/Models/AppDbContext.cs +++ b/API/Models/AppDbContext.cs @@ -4,9 +4,9 @@ namespace API.Models; public partial class AppDbContext(DbContextOptions options) : DbContext(options) { - private DbSet Brands { get; set; } - private DbSet Products { get; set; } - private DbSet ProductTypes { get; set; } + public DbSet Brands { get; set; } + public DbSet Products { get; set; } + public DbSet ProductTypes { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/API/Program.cs b/API/Program.cs index ee00eca..29d7d3c 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -22,4 +22,5 @@ } app.UseHttpsRedirection(); +app.MapControllers(); app.Run(); \ No newline at end of file diff --git a/README.md b/README.md index 8ec7669..b7fa6bc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ docker run -d \ -e POSTGRES_USER=postgres \ -e POSTGRES_HOST_AUTH_METHOD=trust \ -v pgdata_movie:/var/lib/postgresql/data \ + -v ./init.sql:/docker-entrypoint-initdb.d/init.sql \ postgres:16 ``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5e0a808 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + postgres: + image: postgres:16-alpine + container_name: postgres_db + environment: + POSTGRES_DB: movie + POSTGRES_USER: postgres + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - "5432:5432" + volumes: + - ./init.sql:/docker-entrypoint-initdb.d/init.sql diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..815634e --- /dev/null +++ b/init.sql @@ -0,0 +1,275 @@ +INSERT INTO public.brand (brand_name) VALUES + ('Apple'), + ('Samsung'), + ('Sony'), + ('Nike'), + ('Adidas'), + ('Puma'), + ('Zara'), + ('H&M'), + ('IKEA'), + ('Bose'), + ('Dell'), + ('Lenovo'), + ('Logitech'), + ('Canon'), + ('Philips'), + ('Dyson'), + ('Levi''s'), + ('The North Face'), + ('Xiaomi'), + ('Sennheiser'); + +INSERT INTO public.product_type (product_type_name) VALUES + ('Smartphone'), + ('Laptop'), + ('Headphones'), + ('Wireless Earbuds'), + ('Television'), + ('Tablet'), + ('Smartwatch'), + ('Running Shoes'), + ('Sneakers'), + ('T-Shirt'), + ('Pants'), + ('Jacket'), + ('Backpack'), + ('Camera'), + ('Mechanical Keyboard'), + ('Wireless Mouse'), + ('Bluetooth Speaker'), + ('Stick Vacuum Cleaner'), + ('Desk Lamp'), + ('PC Monitor'); + +INSERT INTO public.product ( + product_name, + description, + photo_name, + photo_uri, + brand_id, + product_type_id, + actual_stock, + min_stock, + max_stock +) +VALUES + -- 1. iPhone 15 Pro (Apple / Smartphone) + ( + 'iPhone 15 Pro 128 GB', + 'Smartphone powered by the A17 Pro chip with a titanium frame.', + 'iphone_15_pro.webp', + 'https://assets.example.com/products/apple/iphone-15-pro.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Apple'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Smartphone'), + 45, 10, 100 + ), + + -- 2. Galaxy S24 Ultra (Samsung / Smartphone) + ( + 'Galaxy S24 Ultra 256 GB', + 'Dynamic AMOLED 2X display with integrated S-Pen.', + 'galaxy_s24_ultra.webp', + 'https://assets.example.com/products/samsung/s24-ultra.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Samsung'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Smartphone'), + 30, 8, 80 + ), + + -- 3. WH-1000XM5 (Sony / Headphones) + ( + 'WH-1000XM5 Headphones', + 'Wireless over-ear headphones with industry-leading active noise cancellation.', + 'sony_wh1000xm5.webp', + 'https://assets.example.com/products/sony/wh-1000xm5.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Sony'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Headphones'), + 18, 5, 50 + ), + + -- 4. Air Zoom Pegasus (Nike / Running Shoes) + ( + 'Air Zoom Pegasus 40', + 'Versatile running shoes designed for daily training.', + 'nike_pegasus_40.webp', + 'https://assets.example.com/products/nike/pegasus-40.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Nike'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Running Shoes'), + 65, 15, 120 + ), + + -- 5. Ultraboost Light (Adidas / Sneakers) + ( + 'Ultraboost Light', + 'Responsive Boost cushioning featuring a supportive Primeknit upper.', + 'adidas_ultraboost.webp', + 'https://assets.example.com/products/adidas/ultraboost-light.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Adidas'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Sneakers'), + 40, 10, 90 + ), + + -- 6. QuietComfort Ultra (Bose / Wireless Earbuds) + ( + 'QuietComfort Ultra Earbuds', + 'True wireless earbuds delivering immersive spatial audio.', + 'bose_qc_ultra.webp', + 'https://assets.example.com/products/bose/qc-ultra-earbuds.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Bose'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Wireless Earbuds'), + 25, 5, 60 + ), + + -- 7. XPS 15 (Dell / Laptop) + ( + 'XPS 15 9530', + 'High-performance laptop featuring a 3.5K OLED display and Intel Core i7.', + 'dell_xps_15.webp', + 'https://assets.example.com/products/dell/xps-15.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Dell'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Laptop'), + 12, 3, 30 + ), + + -- 8. ThinkPad X1 Carbon (Lenovo / Laptop) + ( + 'ThinkPad X1 Carbon Gen 11', + 'Ultra-lightweight professional ultrabook built from carbon fiber.', + 'lenovo_x1_carbon.webp', + 'https://assets.example.com/products/lenovo/thinkpad-x1.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Lenovo'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Laptop'), + 15, 4, 35 + ), + + -- 9. MX Master 3S (Logitech / Wireless Mouse) + ( + 'MX Master 3S Performance', + 'Ergonomic wireless mouse with MagSpeed scrolling and quiet clicks.', + 'logitech_mx_master_3s.webp', + 'https://assets.example.com/products/logitech/mx-master-3s.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Logitech'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Wireless Mouse'), + 85, 20, 150 + ), + + -- 10. EOS R6 Mark II (Canon / Camera) + ( + 'EOS R6 Mark II Body Only', + 'Full-frame mirrorless camera with 24.2 MP and 4K 60p video.', + 'canon_eos_r6_mk2.webp', + 'https://assets.example.com/products/canon/eos-r6-mk2.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Canon'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Camera'), + 8, 2, 20 + ), + + -- 11. V15 Detect (Dyson / Stick Vacuum Cleaner) + ( + 'V15 Detect Total Clean', + 'Cordless vacuum cleaner with illumination laser and HEPA filtration.', + 'dyson_v15_detect.webp', + 'https://assets.example.com/products/dyson/v15-detect.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Dyson'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Stick Vacuum Cleaner'), + 22, 5, 50 + ), + + -- 12. 501 Original Jeans (Levi's / Pants) + ( + '501 Original Fit Jeans', + 'Iconic straight fit in 100% cotton denim with button fly.', + 'levis_501_original.webp', + 'https://assets.example.com/products/levis/501-original.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Levi''s'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Pants'), + 110, 25, 200 + ), + + -- 13. Nuptse 1996 Jacket (The North Face / Jacket) + ( + '1996 Retro Nuptse Jacket', + 'Packable down jacket filled with 700-fill goose down.', + 'tnf_nuptse_1996.webp', + 'https://assets.example.com/products/northface/nuptse-1996.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'The North Face'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Jacket'), + 19, 5, 45 + ), + + -- 14. Borealis Backpack (The North Face / Backpack) + ( + 'Borealis Classic Backpack', + '28-liter volume with FlexVent suspension system and laptop compartment.', + 'tnf_borealis.webp', + 'https://assets.example.com/products/northface/borealis.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'The North Face'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Backpack'), + 50, 10, 100 + ), + + -- 15. Tertial Lamp (IKEA / Desk Lamp) + ( + 'TERTIAL Work Lamp', + 'Articulated steel architect lamp with desk clamp.', + 'ikea_tertial.webp', + 'https://assets.example.com/products/ikea/tertial.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'IKEA'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Desk Lamp'), + 140, 30, 250 + ), + + -- 16. Bravia XR A80L (Sony / Television) + ( + 'Bravia XR 55A80L OLED TV', + '55-inch 4K 120Hz OLED TV with Cognitive Processor XR and Google TV.', + 'sony_bravia_a80l.webp', + 'https://assets.example.com/products/sony/bravia-a80l.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Sony'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Television'), + 7, 2, 20 + ), + + -- 17. Hue Go (Philips / Desk Lamp) + ( + 'Hue Go V2 Portable Smart Lamp', + 'Portable White & Color Ambiance smart light compatible with Zigbee/Bluetooth.', + 'philips_hue_go.webp', + 'https://assets.example.com/products/philips/hue-go-v2.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Philips'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Desk Lamp'), + 35, 8, 70 + ), + + -- 18. Redmi Note 13 (Xiaomi / Smartphone) + ( + 'Redmi Note 13 4G', + '120Hz AMOLED display, 108 MP main camera, and 33W fast charging.', + 'xiaomi_redmi_note_13.webp', + 'https://assets.example.com/products/xiaomi/redmi-note-13.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Xiaomi'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Smartphone'), + 75, 15, 150 + ), + + -- 19. Momentum 4 (Sennheiser / Headphones) + ( + 'Momentum 4 Wireless', + 'Audiophile-inspired circum-aural headphones with up to 60-hour battery life.', + 'sennheiser_momentum_4.webp', + 'https://assets.example.com/products/sennheiser/momentum-4.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Sennheiser'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Headphones'), + 14, 4, 30 + ), + + -- 20. Galaxy Tab S9 (Samsung / Tablet) + ( + 'Galaxy Tab S9 Wi-Fi 128 GB', + '11-inch Dynamic AMOLED 2X IP68-rated tablet with included S-Pen.', + 'samsung_tab_s9.webp', + 'https://assets.example.com/products/samsung/tab-s9.webp', + (SELECT brand_id FROM public.brand WHERE brand_name = 'Samsung'), + (SELECT product_type_id FROM public.product_type WHERE product_type_name = 'Tablet'), + 20, 5, 40 + ); From 7470314a0f406095d3e8dc4785a70063bf81e3c9 Mon Sep 17 00:00:00 2001 From: Rached Mejri Date: Sat, 5 Sep 2026 16:46:47 +0200 Subject: [PATCH 2/3] Setup database and CI for tests --- .github/workflows/dotnet.yml | 56 +++++++++++++++++++++++++----------- API/Program.cs | 3 +- API/appsettings.json | 6 +++- Dockerfile-postgres | 1 + docker-compose.yml | 11 +++++-- 5 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 Dockerfile-postgres diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 217f7cb..bf3c485 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,6 +1,3 @@ -# This workflow will build a .NET project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net - name: .NET on: @@ -11,18 +8,45 @@ on: jobs: build: - + env: + ConnectionStrings__DefaultConnection: "Host=localhost;Port=5432;Database=movie;Username=postgres;Password=" + runs-on: ubuntu-latest - + steps: - - uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal + - uses: actions/checkout@v4 + + - name: Start PostgreSQL via Docker Compose + run: | + docker compose up -d --build + until docker compose exec -T postgres pg_isready -U postgres -d movie; do + if [ -z "$(docker compose ps --status running -q postgres)" ]; then + echo "Le conteneur postgres s'est arrêté inopinément. Logs :" + docker compose logs postgres + exit 1 + fi + echo "En attente de PostgreSQL (initialisation en cours)..." + sleep 2 + done + echo "PostgreSQL est prêt !" + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Install dotnet-ef tool + run: dotnet tool install --global dotnet-ef + + - name: Update database + run: dotnet ef database update --project API + + - name: Test + run: dotnet test --no-build --verbosity normal + + - name: Stop Containers + if: always() + run: docker compose down -v \ No newline at end of file diff --git a/API/Program.cs b/API/Program.cs index 29d7d3c..3ac8bf6 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -9,9 +9,8 @@ builder.Services.AddSwaggerGen(); builder.Services.AddControllers(); builder.Services.AddDbContext(options => - options.UseNpgsql("Host=localhost;Port=5432;Database=movie;Username=postgres;Password=") + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")) ); - var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/API/appsettings.json b/API/appsettings.json index 10f68b8..3e9e333 100644 --- a/API/appsettings.json +++ b/API/appsettings.json @@ -5,5 +5,9 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=movie;Username=postgres;Password=" + } } + diff --git a/Dockerfile-postgres b/Dockerfile-postgres new file mode 100644 index 0000000..e9abea8 --- /dev/null +++ b/Dockerfile-postgres @@ -0,0 +1 @@ +FROM postgres:16-alpine diff --git a/docker-compose.yml b/docker-compose.yml index 5e0a808..f74c360 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,17 @@ services: postgres: - image: postgres:16-alpine + build: + context: . + dockerfile: Dockerfile-postgres container_name: postgres_db environment: + POSTGRES_HOST_AUTH_METHOD: "trust" POSTGRES_DB: movie POSTGRES_USER: postgres - POSTGRES_HOST_AUTH_METHOD: trust ports: - "5432:5432" volumes: - - ./init.sql:/docker-entrypoint-initdb.d/init.sql + - pgdata:/var/lib/postgresql/data + +volumes: + pgdata: \ No newline at end of file From 52d73dc5848112f9a0fe884df039c7c0eb4b68a6 Mon Sep 17 00:00:00 2001 From: Rached Mejri Date: Sat, 5 Sep 2026 17:16:53 +0200 Subject: [PATCH 3/3] Creating test project --- .github/workflows/dotnet.yml | 7 +++-- API.Tests/API.Tests.csproj | 20 +++++++++++++ API.Tests/Controller/ProductControllerTest.cs | 30 +++++++++++++++++++ R508.sln | 6 ++++ 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 API.Tests/API.Tests.csproj create mode 100644 API.Tests/Controller/ProductControllerTest.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bf3c485..415bdca 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -37,6 +37,9 @@ jobs: - name: Restore dependencies run: dotnet restore + + - name: Build + run: dotnet build --no-restore - name: Install dotnet-ef tool run: dotnet tool install --global dotnet-ef @@ -45,8 +48,8 @@ jobs: run: dotnet ef database update --project API - name: Test - run: dotnet test --no-build --verbosity normal - + run: dotnet test API.Tests/API.Tests.csproj --no-build --verbosity normal + - name: Stop Containers if: always() run: docker compose down -v \ No newline at end of file diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj new file mode 100644 index 0000000..b0d96df --- /dev/null +++ b/API.Tests/API.Tests.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/API.Tests/Controller/ProductControllerTest.cs b/API.Tests/Controller/ProductControllerTest.cs new file mode 100644 index 0000000..9b7a1d6 --- /dev/null +++ b/API.Tests/Controller/ProductControllerTest.cs @@ -0,0 +1,30 @@ +using API.Controller; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace API.Tests.Controller; + +[TestClass] +[TestSubject(typeof(ProductController))] +public class ProductControllerTest +{ + private readonly ProductController _controller; + + [TestInitialize] + public void Init() + { + + } + + [TestMethod] + public void ShouldCreateProduct() + { + // Given + + // When + + // Then + Assert.AreEqual(1, 1); + } +} \ No newline at end of file diff --git a/R508.sln b/R508.sln index 8fb4245..c226a56 100644 --- a/R508.sln +++ b/R508.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{C3B38DC2-9752-4DE7-B514-13E5D7C5C4A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API.Tests", "API.Tests\API.Tests.csproj", "{78E9942E-3C56-47E4-A41A-95F32FD47256}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {C3B38DC2-9752-4DE7-B514-13E5D7C5C4A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3B38DC2-9752-4DE7-B514-13E5D7C5C4A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3B38DC2-9752-4DE7-B514-13E5D7C5C4A3}.Release|Any CPU.Build.0 = Release|Any CPU + {78E9942E-3C56-47E4-A41A-95F32FD47256}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78E9942E-3C56-47E4-A41A-95F32FD47256}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78E9942E-3C56-47E4-A41A-95F32FD47256}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78E9942E-3C56-47E4-A41A-95F32FD47256}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal