Repository: mdresch/my-portfolio-menno
Pull request #43: Refactor project structure and update models
Author: login: mdresch
Created: 2025-05-21T14:02:03Z | Merged: 2025-05-21T14:14:28Z

- Changed namespaces in PortfolioContext and Project models for consistency.
- Updated Project model to include new properties and removed inheritance from BaseEntity.
- Added ProjectDTO and ProjectService for better data handling and service layer abstraction.
- Implemented view count increment functionality in ProjectService.
- Updated API and frontend components to handle new project properties including view count.
- Removed old solution files and reorganized project structure for clarity.
- Added a new blog post on self-charging cars with key findings.


Diff:
diff --git a/api/PortfolioApi.csproj b/api/PortfolioApi.csproj
deleted file mode 100644
index 5883018c..00000000
--- a/api/PortfolioApi.csproj
+++ /dev/null
@@ -1,47 +0,0 @@
-﻿<Project Sdk="Microsoft.NET.Sdk.Web">
-
-  <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
-    <Nullable>enable</Nullable>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <!-- Azure Best Practice: Configure appropriate TLS settings -->    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-    <NoWarn>NU1903</NoWarn>
-    <UserSecretsId>cdbffb41-7cde-49ca-b10f-4ac2c19aae9d</UserSecretsId>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
-  </PropertyGroup>
-
-  <!-- Note: Azure.Identity 1.11.0 has known vulnerabilities (GHSA-m5vv-6r4h-3vj9) 
-       Consider upgrading once a patched version is available -->  <ItemGroup>
-    <ProjectReference Include="MyPortfolio.ServiceDefaults\MyPortfolio.ServiceDefaults.csproj" />
-    <PackageReference Include="Aspire.Hosting" Version="8.2.2" />
-    <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
-    <PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
-    <PackageReference Include="Azure.Identity" Version="1.14.0" />
-    <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5" />
-    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.5" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-      <PrivateAssets>all</PrivateAssets>
-    </PackageReference>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.5" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.5" />
-    <PackageReference Include="Microsoft.Extensions.Logging.ApplicationInsights" Version="2.23.0" />
-    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.5" />
-    <!-- Removed Microsoft.AspNetCore.Diagnostics.HealthChecks and Microsoft.Extensions.Caching.Memory as suggested by build warnings -->
-  </ItemGroup>
-  
-  <!-- Azure Best Practice: Add package vulnerabilities mitigation and update plan -->
-  <ItemGroup>
-    <None Include="README.md" Pack="true" PackagePath="\" />
-  </ItemGroup>
-
-  <!-- Azure Best Practice: Control package version conflict behavior -->
-  <PropertyGroup>
-    <!-- Report conflicts as errors to avoid runtime issues with Azure services -->
-    <WarningsAsErrors>NU1605</WarningsAsErrors>
-  </PropertyGroup>
-
-</Project>
diff --git a/api/api.ApiService/Configuration/AzureConnectionResilience.cs b/api/api.ApiService/Configuration/AzureConnectionResilience.cs
index e94e94dc..29fc4277 100644
--- a/api/api.ApiService/Configuration/AzureConnectionResilience.cs
+++ b/api/api.ApiService/Configuration/AzureConnectionResilience.cs
@@ -6,6 +6,7 @@
 using Microsoft.Extensions.Diagnostics.HealthChecks;
 using Microsoft.AspNetCore.Diagnostics.HealthChecks;
 using HealthChecks.UI.Client;
+using api.ApiService.Data;
 
 namespace PortfolioApi.Configuration
 {
diff --git a/api/api.ApiService/Controllers/ProjectsController.cs b/api/api.ApiService/Controllers/ProjectsController.cs
index e75e8946..ea89de85 100644
--- a/api/api.ApiService/Controllers/ProjectsController.cs
+++ b/api/api.ApiService/Controllers/ProjectsController.cs
@@ -40,7 +40,8 @@ public async Task<ActionResult<IEnumerable<ProjectDTO>>> GetProjects()
             LiveUrl = p.LiveUrl,
             Created = p.Created,
             Technologies = p.Technologies,
-            Challenges = p.Challenges ?? new List<string>()
+            Challenges = p.Challenges ?? new List<string>(),
+            ViewCount = p.ViewCount // <-- Add this line
         }));
     }
     
@@ -68,7 +69,7 @@ public async Task<ActionResult<ProjectDTO>> GetProject(int id)
             LiveUrl = project.LiveUrl,
             Created = project.Created,
             Technologies = project.Technologies,
-            Challenges = project.Challenges ?? new List<string>()
+            Challenges = project.Challenges ?? new List<string>(),
             ViewCount = project.ViewCount
         };
     }
@@ -185,6 +186,34 @@ public async Task<IActionResult> DeleteProject(int id)
         return NoContent();
     }
     
+    [HttpPost("{slug}/increment-view")]
+    public async Task<IActionResult> IncrementView(string slug)
+    {
+[HttpPost("{slug}/increment-view")]
+    public async Task<IActionResult> IncrementView(string slug)
+    {
+        var affected = await _context.Projects
+            .Where(p => p.Slug == slug)
+            .ExecuteUpdateAsync(p => p.SetProperty(x => x.ViewCount, x => x.ViewCount + 1));
+
+        if (affected == 0)
+        {
+            return NotFound();
+        }
+
+        return NoContent();
+    }
+        if (project == null)
+        {
+            return NotFound();
+        }
+
+        project.ViewCount++;
+        await _context.SaveChangesAsync();
+
+        return NoContent();
+    }
+    
     private bool ProjectExists(int id)
     {
         return _context.Projects.Any(p => p.Id == id);
diff --git a/api/api.ApiService/DTOs/PortfolioDTOs.cs b/api/api.ApiService/DTOs/PortfolioDTOs.cs
deleted file mode 100644
index 7358a70e..00000000
--- a/api/api.ApiService/DTOs/PortfolioDTOs.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-namespace PortfolioApi.DTOs;
-
-// Project DTOs
-public class ProjectDTO
-{
-    public int Id { get; set; }
-    public string Title { get; set; } = null!;
-    public string? Description { get; set; }
-    public string? ImageUrl { get; set; }
-    public string? GitHubUrl { get; set; }
-    public string? LiveUrl { get; set; }
-    public DateTime Created { get; set; }
-    public List<string>? Technologies { get; set; }
-    public List<string>? Challenges { get; set; }
-    public int ViewCount { get; set; }
-}
-
-public class CreateProjectDTO
-{
-    public required string Title { get; set; }
-    public string? Description { get; set; }
-    public string? ImageUrl { get; set; }
-    public string? GitHubUrl { get; set; }
-    public string? LiveUrl { get; set; }
-    public List<string>? Technologies { get; set; }
-    public List<string>? Challenges { get; set; }
-}
-
-public class UpdateProjectDTO
-{
-    public string? Title { get; set; }
-    public string? Description { get; set; }
-    public string? ImageUrl { get; set; }
-    public string? GitHubUrl { get; set; }
-    public string? LiveUrl { get; set; }
-    public List<string>? Technologies { get; set; }
-    public List<string>? Challenges { get; set; }
-}
-
-// Contact Message DTOs
-public class ContactMessageDTO
-{
-    public int? Id { get; set; } // Optional as it may not be returned to public API
-    public string Name { get; set; } = null!;
-    public string? Email { get; set; } // Optional as it might be hidden in public responses
-    public string? Subject { get; set; }
-    public string? Message { get; set; }
-    public DateTime ReceivedAt { get; set; }
-    public bool? IsRead { get; set; }
-}
-
-public class CreateContactMessageDTO
-{
-    public required string Name { get; set; }
-    public required string Email { get; set; }
-    public string? Subject { get; set; }
-    public required string Message { get; set; }
-}
-
-// Skill DTOs
-public class SkillDTO
-{
-    public int Id { get; set; }
-    public string Name { get; set; } = null!;
-    public string? Category { get; set; }
-    public int ProficiencyLevel { get; set; }
-    public string? IconUrl { get; set; }
-}
-
-public class CreateSkillDTO
-{
-    public required string Name { get; set; }
-    public string? Category { get; set; }
-    public int ProficiencyLevel { get; set; }
-    public string? IconUrl { get; set; }
-}
-
-public class UpdateSkillDTO
-{
-    public string? Name { get; set; }
-    public string? Category { get; set; }
-    public int? ProficiencyLevel { get; set; }
-    public string? IconUrl { get; set; }
-}
diff --git a/api/api.ApiService/DTOs/ProjectDTO.cs b/api/api.ApiService/DTOs/ProjectDTO.cs
new file mode 100644
index 00000000..0da50b94
--- /dev/null
+++ b/api/api.ApiService/DTOs/ProjectDTO.cs
@@ -0,0 +1,85 @@
+namespace api.ApiService.DTOs
+{
+    // Project DTOs
+    public class ProjectDTO
+    {
+        public int Id { get; set; }
+        public string Title { get; set; } = null!;
+        public string? Description { get; set; }
+        public string? ImageUrl { get; set; }
+        public string? GitHubUrl { get; set; }
+        public string? LiveUrl { get; set; }
+        public DateTime Created { get; set; }
+        public List<string>? Technologies { get; set; }
+        public List<string>? Challenges { get; set; }
+        public int ViewCount { get; set; }
+    }
+
+    public class CreateProjectDTO
+    {
+        public required string Title { get; set; }
+        public string? Description { get; set; }
+        public string? ImageUrl { get; set; }
+        public string? GitHubUrl { get; set; }
+        public string? LiveUrl { get; set; }
+        public List<string>? Technologies { get; set; }
+        public List<string>? Challenges { get; set; }
+    }
+
+    public class UpdateProjectDTO
+    {
+        public string? Title { get; set; }
+        public string? Description { get; set; }
+        public string? ImageUrl { get; set; }
+        public string? GitHubUrl { get; set; }
+        public string? LiveUrl { get; set; }
+        public List<string>? Technologies { get; set; }
+        public List<string>? Challenges { get; set; }
+    }
+
+    // Contact Message DTOs
+    public class ContactMessageDTO
+    {
+        public int? Id { get; set; } // Optional as it may not be returned to public API
+        public string Name { get; set; } = null!;
+        public string? Email { get; set; } // Optional as it might be hidden in public responses
+        public string? Subject { get; set; }
+        public string? Message { get; set; }
+        public DateTime ReceivedAt { get; set; }
+        public bool? IsRead { get; set; }
+    }
+
+    public class CreateContactMessageDTO
+    {
+        public required string Name { get; set; }
+        public required string Email { get; set; }
+        public string? Subject { get; set; }
+        public required string Message { get; set; }
+    }
+
+    // Skill DTOs
+    public class SkillDTO
+    {
+        public int Id { get; set; }
+        public string Name { get; set; } = null!;
+        public string? Category { get; set; }
+        public int ProficiencyLevel { get; set; }
+        public string? IconUrl { get; set; }
+    }
+
+    public class CreateSkillDTO
+    {
+        public required string Name { get; set; }
+        public string? Category { get; set; }
+        public int ProficiencyLevel { get; set; }
+        public string? IconUrl { get; set; }
+    }
+
+    public class UpdateSkillDTO
+    {
+        public string? Name { get; set; }
+        public string? Category { get; set; }
+        public int? ProficiencyLevel { get; set; }
+        public string? IconUrl { get; set; }
+    }
+}
diff --git a/api/api.ApiService/Data/PortfolioContext.cs b/api/api.ApiService/Data/PortfolioContext.cs
index a96331ea..584ccb20 100644
--- a/api/api.ApiService/Data/PortfolioContext.cs
+++ b/api/api.ApiService/Data/PortfolioContext.cs
@@ -8,7 +8,7 @@
 using Microsoft.EntityFrameworkCore.Design;
 using Microsoft.Extensions.Configuration;
 
-namespace PortfolioApi.Data
+namespace api.ApiService.Data
 {
     /// <summary>
     /// Database context for portfolio application using Azure SQL
diff --git a/api/api.ApiService/Models/Project.cs b/api/api.ApiService/Models/Project.cs
index 830046b4..8d7608ef 100644
--- a/api/api.ApiService/Models/Project.cs
+++ b/api/api.ApiService/Models/Project.cs
@@ -4,49 +4,53 @@
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Text.Json;
 
-namespace PortfolioApi.Models
+namespace api.ApiService.Models
 {
     /// <summary>
     /// Represents a portfolio project with Azure best practices
     /// </summary>
-    public class Project : BaseEntity
+    public class Project
     {
+        public int Id { get; set; }
+        
         // Rename to match controller expectations
         [Required]
         [MaxLength(100)]
         public string Title { get; set; } = null!;
-        
+
+        public string Slug { get; set; } = null!;
+
         [Required]
         public string Description { get; set; } = null!;
-        
+
         // Azure Best Practice: Store as JSON for flexible schema and easy querying
         [Column(TypeName = "nvarchar(MAX)")]
         public string? TechnologiesJson { get; set; }
-        
+
         [NotMapped]
-        public List<string>? Technologies 
+        public List<string>? Technologies
         {
-            get => TechnologiesJson != null ? 
+            get => TechnologiesJson != null ?
                 JsonSerializer.Deserialize<List<string>>(TechnologiesJson) : null;
-            set => TechnologiesJson = value != null ? 
+            set => TechnologiesJson = value != null ?
                 JsonSerializer.Serialize(value) : null;
         }
-        
+
         // Rename to match controller expectations
         public string? GitHubUrl { get; set; }
-        
+
         // Rename to match controller expectations
         public string? LiveUrl { get; set; }
-        
+
         public string? ImageUrl { get; set; }
-        
+
         public List<string>? Challenges { get; set; }
-        
+
         // Azure Best Practice: Add fields for sorting and filtering
         public bool IsFeatured { get; set; } = false;
-        
+
         public int DisplayOrder { get; set; } = 0;
-        
+
         // For backward compatibility
         [NotMapped]
         public string? Name
@@ -54,19 +58,21 @@ public string? Name
             get => Title;
             set => Title = value ?? string.Empty;
         }
-        
+
         [NotMapped]
         public string? ProjectUrl
         {
             get => LiveUrl;
             set => LiveUrl = value;
         }
-        
+
         [NotMapped]
         public string? GithubUrl
         {
             get => GitHubUrl;
             set => GitHubUrl = value;
         }
+        
+        public int ViewCount { get; set; }
     }
 }
\ No newline at end of file
diff --git a/api/api.ApiService/Services/ProjectService.cs b/api/api.ApiService/Services/ProjectService.cs
new file mode 100644
index 00000000..4c0c3e31
--- /dev/null
+++ b/api/api.ApiService/Services/ProjectService.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+using Microsoft.Extensions.Logging; // Import for logging
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+        private readonly ILogger<ProjectService> _logger;
+
+        public ProjectService(PortfolioContext context, ILogger<ProjectService> logger)
+        {
+            _context = context;
+            _logger = logger;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            _logger.LogInformation("Entering GetAllAsync method");
+            try
+            {
+                var projects = await _context.Projects.ToListAsync();
+                var projectDTOs = projects.Select(p => new ProjectDTO
+                {
+                    Id = p.Id,
+                    Title = p.Title,
+                    Description = p.Description,
+                    ImageUrl = p.ImageUrl,
+                    GitHubUrl = p.GitHubUrl,
+                    LiveUrl = p.LiveUrl,
+                    Created = p.Created,
+                    Technologies = p.Technologies,
+                    Challenges = p.Challenges ?? new List<string>(),
+                    ViewCount = p.ViewCount
+                }).ToList();
+                _logger.LogInformation($"GetAllAsync method completed. Retrieved {projectDTOs.Count} projects");
+                return projectDTOs;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error occurred in GetAllAsync method");
+                throw;
+            }
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            try
+            {
+                var projects = await _context.Projects.ToListAsync();
+                return projects.Select(p => new ProjectDTO
+                {
+                    Id = p.Id,
+                    Title = p.Title,
+                    Description = p.Description,
+                    ImageUrl = p.ImageUrl,
+                    GitHubUrl = p.GitHubUrl,
+                    LiveUrl = p.LiveUrl,
+                    Created = p.Created,
+                    Technologies = p.Technologies,
+                    Challenges = p.Challenges ?? new List<string>(),
+                    ViewCount = p.ViewCount
+                }).ToList();
+            }
+            catch (Exception ex)
+            {
+                // TODO: Implement proper logging
+                Console.WriteLine($"Error in GetAllAsync: {ex.Message}");
+                throw;
+            }
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            // Using parameterized query to prevent NoSQL injection
+            var project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == id);
+            if (project == null) return null;
+
+            return new ProjectDTO
+            if (project == null) return null;
+
+var project = await _context.Projects.FindAsync(id);
+            if (project == null) return null;
+
+            // TODO: Implement mapping using AutoMapper
+            return MapProjectToDTO(project);
+        }
+
+        public async Task IncrementViewCountAsync(int id)
+            {
+                Id = project.Id,
+                Title = project.Title,
+                Description = project.Description,
+                ImageUrl = project.ImageUrl,
+                GitHubUrl = project.GitHubUrl,
+                LiveUrl = project.LiveUrl,
+                Created = project.Created,
+                Technologies = project.Technologies,
+                Challenges = project.Challenges ?? new List<string>(),
+                ViewCount = project.ViewCount
+            };
+        }
+
+        public async Task IncrementViewCountAsync(int id)
+        {
+public async Task IncrementViewCountAsync(int id)
+        {
+            // using System.Data.Entity; // Import EntityFramework for DbSet<T>
+            var project = await _context.Projects.FindAsync(id);
+            if (project != null)
+            {
+            if (project != null)
+            {
+                project.ViewCount++;
+                await _context.SaveChangesAsync();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/api/api.ApiService/obj/Debug/net8.0/api.ApiService.sourcelink.json b/api/api.ApiService/obj/Debug/net8.0/api.ApiService.sourcelink.json
index eb12bd6f..e16a7a68 100644
--- a/api/api.ApiService/obj/Debug/net8.0/api.ApiService.sourcelink.json
+++ b/api/api.ApiService/obj/Debug/net8.0/api.ApiService.sourcelink.json
@@ -1 +1 @@
-{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/d2932015c66ddc635c5c4d405b3614267a9d98df/*"}}
\ No newline at end of file
+{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/a379ebde5b3cade346825e2c6f61a4a2227e772c/*"}}
\ No newline at end of file
diff --git a/api/api.AppHost/obj/Debug/net8.0/api.AppHost.AssemblyInfo.cs b/api/api.AppHost/obj/Debug/net8.0/api.AppHost.AssemblyInfo.cs
index b3dff814..e83ff4ec 100644
--- a/api/api.AppHost/obj/Debug/net8.0/api.AppHost.AssemblyInfo.cs
+++ b/api/api.AppHost/obj/Debug/net8.0/api.AppHost.AssemblyInfo.cs
@@ -23,7 +23,7 @@
 [assembly: System.Reflection.AssemblyCompanyAttribute("api.AppHost")]
 [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d2932015c66ddc635c5c4d405b3614267a9d98df")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a379ebde5b3cade346825e2c6f61a4a2227e772c")]
 [assembly: System.Reflection.AssemblyProductAttribute("api.AppHost")]
 [assembly: System.Reflection.AssemblyTitleAttribute("api.AppHost")]
 [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
diff --git a/api/api.AppHost/obj/Debug/net8.0/api.AppHost.sourcelink.json b/api/api.AppHost/obj/Debug/net8.0/api.AppHost.sourcelink.json
index eb12bd6f..e16a7a68 100644
--- a/api/api.AppHost/obj/Debug/net8.0/api.AppHost.sourcelink.json
+++ b/api/api.AppHost/obj/Debug/net8.0/api.AppHost.sourcelink.json
@@ -1 +1 @@
-{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/d2932015c66ddc635c5c4d405b3614267a9d98df/*"}}
\ No newline at end of file
+{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/a379ebde5b3cade346825e2c6f61a4a2227e772c/*"}}
\ No newline at end of file
diff --git a/api/api.AppHost/obj/api.AppHost.csproj.EntityFrameworkCore.targets b/api/api.AppHost/obj/api.AppHost.csproj.EntityFrameworkCore.targets
new file mode 100644
index 00000000..7d6485dc
--- /dev/null
+++ b/api/api.AppHost/obj/api.AppHost.csproj.EntityFrameworkCore.targets
@@ -0,0 +1,28 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Target Name="GetEFProjectMetadata">
+    <MSBuild Condition=" '$(TargetFramework)' == '' "
+             Projects="$(MSBuildProjectFile)"
+             Targets="GetEFProjectMetadata"
+             Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);EFProjectMetadataFile=$(EFProjectMetadataFile)" />
+    <ItemGroup Condition=" '$(TargetFramework)' != '' ">
+      <EFProjectMetadata Include="AssemblyName: $(AssemblyName)" />
+      <EFProjectMetadata Include="Language: $(Language)" />
+      <EFProjectMetadata Include="OutputPath: $(OutputPath)" />
+      <EFProjectMetadata Include="Platform: $(Platform)" />
+      <EFProjectMetadata Include="PlatformTarget: $(PlatformTarget)" />
+      <EFProjectMetadata Include="ProjectAssetsFile: $(ProjectAssetsFile)" />
+      <EFProjectMetadata Include="ProjectDir: $(ProjectDir)" />
+      <EFProjectMetadata Include="RootNamespace: $(RootNamespace)" />
+      <EFProjectMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" />
+      <EFProjectMetadata Include="TargetFileName: $(TargetFileName)" />
+      <EFProjectMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" />
+      <EFProjectMetadata Include="Nullable: $(Nullable)" />
+      <EFProjectMetadata Include="TargetFramework: $(TargetFramework)" />
+      <EFProjectMetadata Include="TargetPlatformIdentifier: $(TargetPlatformIdentifier)" />
+    </ItemGroup>
+    <WriteLinesToFile Condition=" '$(TargetFramework)' != '' "
+                      File="$(EFProjectMetadataFile)"
+                      Lines="@(EFProjectMetadata)" />
+  </Target>
+</Project>
diff --git a/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.AssemblyInfo.cs b/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.AssemblyInfo.cs
index db1e8178..38f0fe6d 100644
--- a/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.AssemblyInfo.cs
+++ b/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.AssemblyInfo.cs
@@ -13,7 +13,7 @@
 [assembly: System.Reflection.AssemblyCompanyAttribute("api.ServiceDefaults")]
 [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d2932015c66ddc635c5c4d405b3614267a9d98df")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a379ebde5b3cade346825e2c6f61a4a2227e772c")]
 [assembly: System.Reflection.AssemblyProductAttribute("api.ServiceDefaults")]
 [assembly: System.Reflection.AssemblyTitleAttribute("api.ServiceDefaults")]
 [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
diff --git a/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.sourcelink.json b/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.sourcelink.json
index eb12bd6f..e16a7a68 100644
--- a/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.sourcelink.json
+++ b/api/api.ServiceDefaults/obj/Debug/net8.0/api.ServiceDefaults.sourcelink.json
@@ -1 +1 @@
-{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/d2932015c66ddc635c5c4d405b3614267a9d98df/*"}}
\ No newline at end of file
+{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/a379ebde5b3cade346825e2c6f61a4a2227e772c/*"}}
\ No newline at end of file
diff --git a/api/api.Web/obj/Debug/net8.0/api.Web.AssemblyInfo.cs b/api/api.Web/obj/Debug/net8.0/api.Web.AssemblyInfo.cs
index 40b6e41d..719786c4 100644
--- a/api/api.Web/obj/Debug/net8.0/api.Web.AssemblyInfo.cs
+++ b/api/api.Web/obj/Debug/net8.0/api.Web.AssemblyInfo.cs
@@ -13,7 +13,7 @@
 [assembly: System.Reflection.AssemblyCompanyAttribute("api.Web")]
 [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d2932015c66ddc635c5c4d405b3614267a9d98df")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a379ebde5b3cade346825e2c6f61a4a2227e772c")]
 [assembly: System.Reflection.AssemblyProductAttribute("api.Web")]
 [assembly: System.Reflection.AssemblyTitleAttribute("api.Web")]
 [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
diff --git a/api/api.Web/obj/Debug/net8.0/api.Web.sourcelink.json b/api/api.Web/obj/Debug/net8.0/api.Web.sourcelink.json
index eb12bd6f..e16a7a68 100644
--- a/api/api.Web/obj/Debug/net8.0/api.Web.sourcelink.json
+++ b/api/api.Web/obj/Debug/net8.0/api.Web.sourcelink.json
@@ -1 +1 @@
-{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/d2932015c66ddc635c5c4d405b3614267a9d98df/*"}}
\ No newline at end of file
+{"documents":{"C:\\Users\\menno\\Source\\Repos\\my-portfolio-menno\\*":"https://raw.githubusercontent.com/mdresch/my-portfolio-menno/a379ebde5b3cade346825e2c6f61a4a2227e772c/*"}}
\ No newline at end of file
diff --git a/api/api.sln b/api/api.sln
deleted file mode 100644
index 7b4d186f..00000000
--- a/api/api.sln
+++ /dev/null
@@ -1,42 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.8.0.0
-MinimumVisualStudioVersion = 17.8.0.0
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api.AppHost", "api.AppHost\api.AppHost.csproj", "{C2E89898-06CF-494E-BA27-C35A58DBB084}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api.ServiceDefaults", "api.ServiceDefaults\api.ServiceDefaults.csproj", "{5D9DB12A-ACF1-4C3D-A1FD-A1F101CF40EC}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api.ApiService", "api.ApiService\api.ApiService.csproj", "{0E0E8ED7-78F3-41D3-9539-79E4AB1E79F4}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api.Web", "api.Web\api.Web.csproj", "{C71F0908-9035-4FE4-8D3D-73F2E45CE8BE}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{C2E89898-06CF-494E-BA27-C35A58DBB084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C2E89898-06CF-494E-BA27-C35A58DBB084}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C2E89898-06CF-494E-BA27-C35A58DBB084}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C2E89898-06CF-494E-BA27-C35A58DBB084}.Release|Any CPU.Build.0 = Release|Any CPU
-		{5D9DB12A-ACF1-4C3D-A1FD-A1F101CF40EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{5D9DB12A-ACF1-4C3D-A1FD-A1F101CF40EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{5D9DB12A-ACF1-4C3D-A1FD-A1F101CF40EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{5D9DB12A-ACF1-4C3D-A1FD-A1F101CF40EC}.Release|Any CPU.Build.0 = Release|Any CPU
-		{0E0E8ED7-78F3-41D3-9539-79E4AB1E79F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{0E0E8ED7-78F3-41D3-9539-79E4AB1E79F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{0E0E8ED7-78F3-41D3-9539-79E4AB1E79F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{0E0E8ED7-78F3-41D3-9539-79E4AB1E79F4}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C71F0908-9035-4FE4-8D3D-73F2E45CE8BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C71F0908-9035-4FE4-8D3D-73F2E45CE8BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C71F0908-9035-4FE4-8D3D-73F2E45CE8BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C71F0908-9035-4FE4-8D3D-73F2E45CE8BE}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {6C71A77D-7C3E-45B8-85C9-489A6A52571D}
-	EndGlobalSection
-EndGlobal
diff --git a/content/blog/self-charging-cars-key-findings-21052025.md b/content/blog/self-charging-cars-key-findings-21052025.md
new file mode 100644
index 00000000..05dd92a4
--- /dev/null
+++ b/content/blog/self-charging-cars-key-findings-21052025.md
@@ -0,0 +1,105 @@
+---
+title: "Self-Charging Car: Key Findings - May 21, 2025"
+date: "2025-05-21"
+author: "Menno Drescher"
+categories:
+  - Technology
+  - Electric Vehicles
+excerpt: "Mitsubishi Shifts EV Strategy, Partners with Foxconn"
+coverImage: "/images/hero-image-techcorp.jpg"
+---
+
+## Mitsubishi Shifts EV Strategy, Partners with Foxconn
+
+**May 21, 2025**
+
+### Overview
+
+- **Mitsubishi** opts to work with **Foxconn** on EV development, canceling its Ampere investment agreement and signaling economic strain within the Renault-Nissan-Mitsubishi Alliance.
+- **Avantium NV** partners with Bottle Collective to develop Dry Molded Fiber bottles using its plant-based polymer PEF, enhancing sustainability and performance.
+- **Waymo LLC** reaches 10 million paid robotaxi trips, doubling in the past five months, and expands its autonomous ride-hailing service to San Jose.
+- **Mercedes-Benz Group AG** showcases a new CLA model with 10-minute charging capability and announces a EUR5 billion stock buyback plan.
+- **Sono Motors GmbH** reports first revenues following a financial turnaround, decreases cash used in operating activities, and commences its first Bus OEM factory partnership.
+- **BMW** introduces a new i7 prototype with a solid state battery pack and approves a third share buyback programme of up to EUR2 billion.
+- **Alphabet Inc.** launches Google AI Ultra, a new high-end AI subscription service, and announces a strategic partnership with Warby Parker for AI-powered glasses.
+- **Amazon.com Inc.** expands its footprint in Davenport with a new last-mile delivery station, expected to create over 100 jobs.
+
+---
+
+### Renault-Nissan-Mitsubishi Alliance
+
+The Renault-Nissan-Mitsubishi Alliance is showing signs of economic strain as Mitsubishi chooses to collaborate with Foxconn on electric vehicle development, rather than working with its traditional alliance partners. Mitsubishi's cancellation of its Ampere investment agreement further illustrates the financial pressures within the alliance.
+
+---
+
+### Avantium NV (AVTX)
+
+Avantium NV has announced a partnership with the Bottle Collective to accelerate the development of Dry Molded Fiber (DMF) bottles using its plant-based polymer PEF. This collaboration aims to enhance sustainability and performance, with PEF offering superior barrier properties and mechanical strength. Avantium highlights PEF's 100% plant-based, fully recyclable nature, supporting a circular economy. Director of Business Development Bineke Posthumus emphasizes the potential of PEF in collaboration with global brands like Opella, Diageo, and Haleon.
+
+---
+
+### Nissan Motor Co. Ltd. (7201)
+
+Nissan faces significant economic implications from the closure of its Oppama and Shonan plants in Japan, impacting local communities and the company's EV production capabilities.
+
+---
+
+### Waymo LLC
+
+Waymo LLC (GOOGL) has reached 10 million paid robotaxi trips, doubling in just five months. Co-CEO Tekedra Mawakana announced at Google I/O that Waymo has received approval to expand its autonomous ride-hailing service to more of the San Francisco Bay Area, including San Jose. Despite a 9% year-over-year drop in revenue and increased operating losses in Q1 2025, Waymo is focused on building a sustainable, profitable business model.
+
+---
+
+### Mercedes-Benz Group AG (MBG)
+
+Mercedes-Benz has unveiled a new CLA model featuring 10-minute charging capability, adding 325 kilometers of range with 800-volt systems. The company also announced a EUR5 billion stock buyback plan, potentially reducing its stake in Daimler Truck Holding to fund the buyback.
+
+---
+
+### Sono Motors GmbH
+
+Sono Group N.V. reported a net income of EUR8.8 million for Q1 2025, driven by non-cash fair value adjustments to convertible debt instruments. The company recorded its first revenues after a financial turnaround and decreased cash used in operating activities to EUR2.2 million. Sono Motors commenced its first Bus OEM factory partnership and began collaborating with Ford under the EU-funded SolarMoves project to test Vehicle Integrated Photovoltaics (VIPV) on a Ford E-Transit. The company is intensifying its focus on OEM collaborations and industrial partnerships.
+
+---
+
+### Volkswagen AG (VOW)
+
+Volkswagen has reduced its headcount in Germany by around 7,000 since late 2023, aiming for annual cost savings of up to EUR15 billion.
+
+---
+
+### Ferrari N.V. (RACE)
+
+Ferrari's hybrid strategy focuses on fewer cars per employee but higher revenue per unit, emphasizing premium, high-performance vehicles. The in-house design center has fostered closer collaboration between designers and engineers, leading to more integrated and technically advanced vehicles.
+
+---
+
+### Volvo AB (VOLV.B)
+
+Volvo AB introduces the new FH Aero Electric long-distance truck, offering up to 600 kilometers of range and fast-charging capabilities, set to transform long-haul transport.
+
+---
+
+### Toyota Motor Corp. (7203)
+
+Toyota is nearing acceptance of a buyout offer from chairman Akio Toyoda, with a potential $41 billion takeover bid expected soon. The company continues its 'multi-pathway' electrification strategy, including hybrids and alternative powertrains, and unveiled the latest RAV4 with the new Arene software platform. Toyota's hybrid models now account for over half of Toyota and Lexus volume in the US for Q1 2025.
+
+---
+
+### Bayerische Motoren Werke AG (BMW)
+
+BMW has introduced a new i7 prototype with a solid state battery pack developed with Solid Power, to be tested around Munich. BMW Korea, Kia, and Hyundai will recall over 16,000 vehicles due to manufacturing defects. BMW's board approved a third share buyback programme of up to EUR2 billion, and the company has increased its market share in New Zealand and reclaimed the top premium segment position.
+
+---
+
+### Amazon.com Inc. (AMZN)
+
+Amazon is expanding in Davenport with a new 114,000 square-foot last-mile delivery station, creating over 100 jobs, and has intensified efforts to combat cargo theft after a major organized crime ring was charged.
+
+---
+
+### Alphabet Inc. (GOOGL)
+
+Alphabet launched Google AI Ultra, a high-end AI subscription service at $249.99/month, and announced a $150 million partnership with Warby Parker to develop AI-powered glasses for Android XR. Alphabet is also facing two major antitrust lawsuits that could lead to a breakup.
+
+---
\ No newline at end of file
diff --git a/my-portfolio-menno.sln b/my-portfolio-menno.sln
deleted file mode 100644
index 35154eb2..00000000
--- a/my-portfolio-menno.sln
+++ /dev/null
@@ -1,56 +0,0 @@
-﻿
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.5.2.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortfolioApi", "api\PortfolioApi.csproj", "{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{D82C56D6-B2CD-F611-9E31-04638F7EC30E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyPortfolio.AppHost", "api\MyPortfolio.AppHost\MyPortfolio.AppHost.csproj", "{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Debug|x64 = Debug|x64
-		Debug|x86 = Debug|x86
-		Release|Any CPU = Release|Any CPU
-		Release|x64 = Release|x64
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Debug|x64.Build.0 = Debug|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Debug|x86.Build.0 = Debug|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Release|Any CPU.Build.0 = Release|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Release|x64.ActiveCfg = Release|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Release|x64.Build.0 = Release|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Release|x86.ActiveCfg = Release|Any CPU
-		{0A213BB9-AF49-5ADF-0E7C-16B9DFDF3ED0}.Release|x86.Build.0 = Release|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Debug|x64.Build.0 = Debug|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Debug|x86.Build.0 = Debug|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Release|Any CPU.Build.0 = Release|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Release|x64.ActiveCfg = Release|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Release|x64.Build.0 = Release|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Release|x86.ActiveCfg = Release|Any CPU
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D}.Release|x86.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(NestedProjects) = preSolution
-		{EB3BA980-6C16-4D13-A219-5DA6CB51B17D} = {D82C56D6-B2CD-F611-9E31-04638F7EC30E}
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {B0BAEDE7-3A2C-4C32-A2D1-982D49F194B9}
-	EndGlobalSection
-EndGlobal
diff --git a/src/app/projects/ProjectCard.tsx b/src/app/projects/ProjectCard.tsx
index f9906b77..146f24d1 100644
--- a/src/app/projects/ProjectCard.tsx
+++ b/src/app/projects/ProjectCard.tsx
@@ -6,7 +6,52 @@ function normalizeSlug(title: string) {
   return title?.toLowerCase().replace(/\s+/g, '-');
 }
 
-export default function ProjectCard({ project, onEdit }) {
+interface Project {
+import Image from "next/image";
+import { useRouter } from "next/navigation";
+
+// Helper to generate a slug from the project title
+function normalizeSlug(title: string) {
+  return title?.toLowerCase().replace(/\s+/g, '-');
+}
+
+interface Project {
+  id: string;
+  slug?: string;
+  title: string;
+  description?: string;
+  imageUrl?: string;
+  image?: string;
+  technologies?: string[];
+  viewCount?: number;
+}
+
+interface ProjectCardProps {
+  project: Project;
+}
+
+export default function ProjectCard({ project }: ProjectCardProps) {
+  const router = useRouter();
+  const slug =
+    project.slug ||
+    (project.title ? normalizeSlug(project.title) : project.id || '');
+
+  return (
+  _id?: string;
+  slug?: string;
+  title: string;
+  description?: string;
+  imageUrl?: string;
+  image?: string;
+  technologies?: string[];
+  viewCount?: number;
+}
+
+interface ProjectCardProps {
+  project: Project;
+}
+
+export default function ProjectCard({ project }: ProjectCardProps) {
   const router = useRouter();
   const slug =
     project.slug ||
@@ -30,12 +75,21 @@ export default function ProjectCard({ project, onEdit }) {
           </span>
         ))}
       </div>
-      <button
-        className="mt-2 text-blue-600 hover:underline"
-        onClick={() => router.push(`/projects/${slug}/edit`)}
-      >
-        Edit
-      </button>
+      <div className="flex items-center gap-4 mt-2">
+        <button
+          className="text-blue-600 hover:underline"
+          onClick={() => router.push(`/projects/${slug}/edit`)}
+        >
+          Edit
+        </button>
+        <span className="text-gray-500 text-sm flex items-center">
+          <svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
+            <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
+            <path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
+          </svg>
+          {project.viewCount ?? 0} views
+        </span>
+      </div>
     </div>
   );
 }
\ No newline at end of file
diff --git a/src/app/projects/ProjectDetail.tsx b/src/app/projects/ProjectDetail.tsx
index 75799e02..44b73951 100644
--- a/src/app/projects/ProjectDetail.tsx
+++ b/src/app/projects/ProjectDetail.tsx
@@ -3,6 +3,7 @@
 import Image from "next/image";
 import CollapsibleSection from "./CollapsibleSection";
 import { useRouter } from "next/navigation";
+import { useEffect } from "react";
 
 interface ProjectDetailProps {
   project: {
@@ -13,7 +14,8 @@ interface ProjectDetailProps {
     gitHubUrl?: string;
     liveUrl?: string;
     challenges?: string[];
-    slug?: string; // Make sure slug is available
+    slug?: string;
+    viewCount?: number; // <-- Add this line
   };
 }
 
@@ -21,6 +23,17 @@ export default function ProjectDetail({ project }: ProjectDetailProps) {
   const router = useRouter();
   const slug = project.slug || project.title?.toLowerCase().replace(/\s+/g, '-');
 
+  useEffect(() => {
+    // Only increment if project has an id or slug
+    if (project.slug) {
+      fetch(`/api/projects/${project.slug}/increment-view`, { method: "POST" });
+    }
+    // If you use project.id, use that instead:
+    // if (project.id) {
+    //   fetch(`/api/projects/${project.id}/increment-view`, { method: "POST" });
+    // }
+  }, [project.slug]);
+
   return (
     <div className="max-w-3xl mx-auto p-6 bg-white dark:bg-neutral-900 rounded-lg shadow">
       <h1 className="text-3xl font-bold mb-2">{project.title}</h1>
@@ -32,7 +45,7 @@ export default function ProjectDetail({ project }: ProjectDetailProps) {
         className="rounded-lg mb-4"
       />
       <p className="mb-4 text-cyan-700 dark:text-cyan-200">{project.description}</p>
-      <div className="mb-4 flex gap-4">
+      <div className="mb-4 flex gap-4 items-center">
         {project.gitHubUrl && (
           <a href={project.gitHubUrl} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">GitHub</a>
         )}
@@ -45,6 +58,13 @@ export default function ProjectDetail({ project }: ProjectDetailProps) {
         >
           Edit
         </button>
+        <span className="text-gray-500 text-sm flex items-center ml-4">
+          <svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
+            <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
+            <path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
+          </svg>
+          {project.viewCount ?? 0} views
+        </span>
       </div>
       <div className="mb-4">
         <strong>Technologies:</strong> {project.technologies.join(", ")}
diff --git a/src/app/projects/ProjectsClient.tsx b/src/app/projects/ProjectsClient.tsx
index 4c1d5ccf..3d23cdee 100644
--- a/src/app/projects/ProjectsClient.tsx
+++ b/src/app/projects/ProjectsClient.tsx
@@ -2,7 +2,22 @@
 import React, { useState, useEffect } from 'react';
 import ProjectForm from '@/components/ProjectForm';
 import { ProjectService } from '@/lib/api-services';
-import type { Project as ApiProject } from '@/types/api';
+// Define ApiProject type locally (copied from src/types/api.ts)
+interface ApiProject {
+  id: number;
+  title: string;
+  description: string | null;
+  imageUrl: string | null;
+  gitHubUrl: string | null;
+  liveUrl: string | null;
+  created: string;
+  technologies: string[];
+  viewCount?: number;
+  caseStudy?: string | null;
+  screenshots?: string[];
+  outcomes?: string[];
+  challenges?: string[];
+}
 import ProjectCard from './ProjectCard';
 
 interface ProjectsClientProps {
diff --git a/src/app/projects/[slug]/page.tsx b/src/app/projects/[slug]/page.tsx
index 36cc9e99..0dd4f733 100644
--- a/src/app/projects/[slug]/page.tsx
+++ b/src/app/projects/[slug]/page.tsx
@@ -1,11 +1,8 @@
-import ProjectDetail from '../ProjectDetail';
-import { ProjectService } from '@/lib/api-services';
+import ProjectDetail from "../ProjectDetail";
+
 import type { Project as ApiProject } from '@/types/api';
 import { notFound } from 'next/navigation';
 
-interface ProjectPageProps {
-  params: { slug: string };
-}
 
 // Helper to normalize API data to ProjectDetail type
 function normalizeProject(p: ApiProject) {
@@ -16,16 +13,40 @@ function normalizeProject(p: ApiProject) {
     imageUrl: p.imageUrl ?? '/default-project-image.png',
     gitHubUrl: p.gitHubUrl ?? '',
     liveUrl: p.liveUrl ?? '',
-    challenges: p.challenges ?? [],
   };
 }
 
-export default async function ProjectPage({ params }: ProjectPageProps) {
-  // Fetch all projects and find by slug (or use a getBySlug API if available)
-  const projects = await ProjectService.getAll();
-  const project = projects.map(normalizeProject).find(
-    (p) => p.title.toLowerCase().replace(/\s+/g, '-') === params.slug
-  );
+export default async function ProjectPage({ params }: { params: { slug: string } }) {
+  // Await params if needed (for Next.js 14+)
+  // const { slug } = await params;
+
+
+  // TODO: Replace this with your actual data fetching logic
+// Await params if needed (for Next.js 14+)
+  // const { slug } = await params;
+
+  let projects: ApiProject[] = [];
+
+  try {
+    // TODO: Replace this with your actual data fetching logic
+    projects = await fetchProjects(); // Example: fetch from API
+  } catch (error) {
+    console.error('Error fetching projects:', error);
+    // Handle the error appropriately (e.g., show an error message to the user)
+    return <div>Error loading projects. Please try again later.</div>;
+  }
+
+  const project = projects
+    .map(normalizeProject)
+
+  const project = projects
+    .map(normalizeProject)
+    .find(
+      (p) => p.title.toLowerCase().replace(/\s+/g, '-') === params.slug
+    );
+
   if (!project) return notFound();
+
+  // Ensure project.viewCount is present
   return <ProjectDetail project={project} />;
 }
diff --git a/src/types/api.ts b/src/types/api.ts
index b0eb776d..6b96bd91 100644
--- a/src/types/api.ts
+++ b/src/types/api.ts
@@ -1,15 +1,19 @@
 // API Types that match the .NET Backend models
 
-export interface Project {
-  id: number;
-  title: string;
-  description: string | null;
-  imageUrl: string | null;
-  gitHubUrl: string | null;
-  liveUrl: string | null;
-  created: string;
-  technologies: string[];
-  viewCount?: number;
+export interface ApiProject {
+id: number;
+title: string;
+description: string | null;
+imageUrl: string | null;
+gitHubUrl: string | null;
+liveUrl: string | null;
+created: string;
+technologies: string[];
+viewCount?: number;
+caseStudy?: string | null;
+screenshots?: string[];
+outcomes?: string[];
+challenges?: string[];
 }
 
 export interface BlogPost {


Reviews:
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:29Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:31Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:32Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:33Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:34Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:35Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:36Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:37Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:38Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:04:40Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:02Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:03Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:04Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:05Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:07Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:08Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:09Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:10Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:11Z
user: login: amazon-q-developer[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:05:22Z

Review comments:
diff_hunk: @@ -16,16 +13,25 @@ function normalizeProject(p: ApiProject) {
     imageUrl: p.imageUrl ?? '/default-project-image.png',
     gitHubUrl: p.gitHubUrl ?? '',
     liveUrl: p.liveUrl ?? '',
-    challenges: p.challenges ?? [],
   };
 }
 
-export default async function ProjectPage({ params }: ProjectPageProps) {
-  // Fetch all projects and find by slug (or use a getBySlug API if available)
-  const projects = await ProjectService.getAll();
-  const project = projects.map(normalizeProject).find(
-    (p) => p.title.toLowerCase().replace(/\s+/g, '-') === params.slug
-  );
+export default async function ProjectPage({ params }: { params: { slug: string } }) {
+  // Await params if needed (for Next.js 14+)
+  // const { slug } = await params;
+
+
+  // TODO: Replace this with your actual data fetching logic
+  const projects: ApiProject[] = []; // Example: fetch from API or import
path: src/app/projects/[slug]/page.tsx
user: login: amazon-q-developer[bot]
body: > [!WARNING]
> ****Description:**** No error handling for potential API fetch errors. Implement try-catch block around data fetching logic to handle potential errors.

****Severity:**** High


created_at: 2025-05-21T14:04:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,69 +4,75 @@
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Text.Json;
 
-namespace PortfolioApi.Models
+namespace api.ApiService.Models
 {
     /// <summary>
     /// Represents a portfolio project with Azure best practices
     /// </summary>
-    public class Project : BaseEntity
+    public class Project
     {
+        public int Id { get; set; }
+        
         // Rename to match controller expectations
         [Required]
         [MaxLength(100)]
         public string Title { get; set; } = null!;
-        
+
+        public string Slug { get; set; } = null!;
+
         [Required]
         public string Description { get; set; } = null!;
-        
+
         // Azure Best Practice: Store as JSON for flexible schema and easy querying
         [Column(TypeName = "nvarchar(MAX)")]
         public string? TechnologiesJson { get; set; }
-        
+
         [NotMapped]
-        public List<string>? Technologies 
+        public List<string>? Technologies
         {
-            get => TechnologiesJson != null ? 
+            get => TechnologiesJson != null ?
                 JsonSerializer.Deserialize<List<string>>(TechnologiesJson) : null;
-            set => TechnologiesJson = value != null ? 
+            set => TechnologiesJson = value != null ?
                 JsonSerializer.Serialize(value) : null;
         }
-        
+
         // Rename to match controller expectations
         public string? GitHubUrl { get; set; }
-        
+
         // Rename to match controller expectations
         public string? LiveUrl { get; set; }
-        
+
         public string? ImageUrl { get; set; }
-        
+
         public List<string>? Challenges { get; set; }
-        
+
         // Azure Best Practice: Add fields for sorting and filtering
         public bool IsFeatured { get; set; } = false;
-        
+
         public int DisplayOrder { get; set; } = 0;
-        
+
         // For backward compatibility
         [NotMapped]
         public string? Name
         {
             get => Title;
             set => Title = value ?? string.Empty;
         }
-        
+
         [NotMapped]
         public string? ProjectUrl
         {
             get => LiveUrl;
             set => LiveUrl = value;
         }
-        
+
         [NotMapped]
         public string? GithubUrl
         {
             get => GitHubUrl;
             set => GitHubUrl = value;
         }
+        
+        public int ViewCount { get; set; }
path: api/api.ApiService/Models/Project.cs
user: login: amazon-q-developer[bot]
body: > [!WARNING]
> ****Description:**** After reviewing your changes for backwards compatibility, the addition of the ViewCount property to the Project model and ProjectDTO may cause issues for existing clients. This change modifies the structure of the API response, potentially breaking clients expecting the old format.

****Severity:**** High


created_at: 2025-05-21T14:04:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: > [!WARNING]
> ****Description:**** The GetAllAsync method lacks error handling for database operations. Implement try-catch block to handle potential exceptions during database query.

****Severity:**** High


created_at: 2025-05-21T14:04:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: > [!WARNING]
> ****Description:**** Potential NoSQL injection risk detected. Untrusted input is being used directly in database queries without proper validation or sanitization. This can allow attackers to manipulate queries, leading to unauthorized data access, modification, or deletion. To mitigate this risk, always validate and sanitize user input, use parameterized queries or prepared statements. Learn more - https://cwe.mitre.org/data/definitions/943.html

****Severity:**** High


created_at: 2025-05-21T14:04:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
+            if (project == null) return null;
+
+            return new ProjectDTO
+            {
+                Id = project.Id,
+                Title = project.Title,
+                Description = project.Description,
+                ImageUrl = project.ImageUrl,
+                GitHubUrl = project.GitHubUrl,
+                LiveUrl = project.LiveUrl,
+                Created = project.Created,
+                Technologies = project.Technologies,
+                Challenges = project.Challenges ?? new List<string>(),
+                ViewCount = project.ViewCount
+            };
+        }
+
+        public async Task IncrementViewCountAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: > [!WARNING]
> ****Description:**** Potential NoSQL injection risk detected. Untrusted input is being used directly in database queries without proper validation or sanitization. This can allow attackers to manipulate queries, leading to unauthorized data access, modification, or deletion. To mitigate this risk, always validate and sanitize user input, use parameterized queries or prepared statements. Learn more - https://cwe.mitre.org/data/definitions/943.html

****Severity:**** High


created_at: 2025-05-21T14:04:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,6 +186,21 @@ public async Task<IActionResult> DeleteProject(int id)
         return NoContent();
     }
     
+    [HttpPost("{slug}/increment-view")]
+    public async Task<IActionResult> IncrementView(string slug)
+    {
+        var project = await _context.Projects.FirstOrDefaultAsync(p => p.Slug == slug);
path: api/api.ApiService/Controllers/ProjectsController.cs
user: login: amazon-q-developer[bot]
body: ****Description:**** The IncrementView method performs two separate database operations. Consider using a single database operation to update the ViewCount.

****Severity:**** Medium


created_at: 2025-05-21T14:04:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: ****Description:**** The GetAllAsync method lacks logging for tracking method execution and potential issues. Consider adding logging statements to track method entry, exit, and any potential errors.

****Severity:**** Medium


created_at: 2025-05-21T14:04:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,7 +6,23 @@ function normalizeSlug(title: string) {
   return title?.toLowerCase().replace(/\s+/g, '-');
 }
 
-export default function ProjectCard({ project, onEdit }) {
+interface Project {
+  id?: string;
path: src/app/projects/ProjectCard.tsx
user: login: amazon-q-developer[bot]
body: ****Description:**** Multiple optional ID fields (id, _id) in the Project interface may lead to confusion. Consider using a single ID field, preferably 'id', and ensure consistent usage throughout the application.

****Severity:**** Medium


created_at: 2025-05-21T14:04:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
+            if (project == null) return null;
+
+            return new ProjectDTO
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: ****Description:**** The method manually maps Project entity to ProjectDTO, which could lead to inconsistencies if the mapping is repeated elsewhere. Consider using a mapping library like AutoMapper to centralize and simplify entity-to-DTO mappings.

****Severity:**** Medium


created_at: 2025-05-21T14:04:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,14 +14,26 @@ interface ProjectDetailProps {
     gitHubUrl?: string;
     liveUrl?: string;
     challenges?: string[];
-    slug?: string; // Make sure slug is available
+    slug?: string;
+    viewCount?: number; // <-- Add this line
   };
 }
 
 export default function ProjectDetail({ project }: ProjectDetailProps) {
   const router = useRouter();
   const slug = project.slug || project.title?.toLowerCase().replace(/\s+/g, '-');
 
+  useEffect(() => {
path: src/app/projects/ProjectDetail.tsx
user: login: amazon-q-developer[bot]
body: ****Description:**** The useEffect hook depends on project.slug, but it's not included in the dependency array, potentially causing stale closures. Add project.slug to the useEffect dependency array to ensure the effect runs when the slug changes.

****Severity:**** Medium


created_at: 2025-05-21T14:04:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,16 +13,25 @@ function normalizeProject(p: ApiProject) {
     imageUrl: p.imageUrl ?? '/default-project-image.png',
     gitHubUrl: p.gitHubUrl ?? '',
     liveUrl: p.liveUrl ?? '',
-    challenges: p.challenges ?? [],
   };
 }
 
-export default async function ProjectPage({ params }: ProjectPageProps) {
-  // Fetch all projects and find by slug (or use a getBySlug API if available)
-  const projects = await ProjectService.getAll();
-  const project = projects.map(normalizeProject).find(
-    (p) => p.title.toLowerCase().replace(/\s+/g, '-') === params.slug
-  );
+export default async function ProjectPage({ params }: { params: { slug: string } }) {
+  // Await params if needed (for Next.js 14+)
+  // const { slug } = await params;
+
+
+  // TODO: Replace this with your actual data fetching logic
+  const projects: ApiProject[] = []; // Example: fetch from API or import
path: src/app/projects/[slug]/page.tsx
user: login: amazon-q-developer[bot]
body: The fix addresses the lack of error handling for potential API fetch errors by implementing a try-catch block around the data fetching logic. It wraps the project fetching in a try block, catches any errors, logs them, and returns an error message to the user. This ensures that the application gracefully handles potential API failures or network issues, improving its robustness and user experience.
```suggestion
// Await params if needed (for Next.js 14+)
  // const { slug } = await params;

  let projects: ApiProject[] = [];

  try {
    // TODO: Replace this with your actual data fetching logic
    projects = await fetchProjects(); // Example: fetch from API
  } catch (error) {
    console.error('Error fetching projects:', error);
    // Handle the error appropriately (e.g., show an error message to the user)
    return <div>Error loading projects. Please try again later.</div>;
  }

  const project = projects
    .map(normalizeProject)
```

created_at: 2025-05-21T14:05:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,69 +4,75 @@
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Text.Json;
 
-namespace PortfolioApi.Models
+namespace api.ApiService.Models
 {
     /// <summary>
     /// Represents a portfolio project with Azure best practices
     /// </summary>
-    public class Project : BaseEntity
+    public class Project
     {
+        public int Id { get; set; }
+        
         // Rename to match controller expectations
         [Required]
         [MaxLength(100)]
         public string Title { get; set; } = null!;
-        
+
+        public string Slug { get; set; } = null!;
+
         [Required]
         public string Description { get; set; } = null!;
-        
+
         // Azure Best Practice: Store as JSON for flexible schema and easy querying
         [Column(TypeName = "nvarchar(MAX)")]
         public string? TechnologiesJson { get; set; }
-        
+
         [NotMapped]
-        public List<string>? Technologies 
+        public List<string>? Technologies
         {
-            get => TechnologiesJson != null ? 
+            get => TechnologiesJson != null ?
                 JsonSerializer.Deserialize<List<string>>(TechnologiesJson) : null;
-            set => TechnologiesJson = value != null ? 
+            set => TechnologiesJson = value != null ?
                 JsonSerializer.Serialize(value) : null;
         }
-        
+
         // Rename to match controller expectations
         public string? GitHubUrl { get; set; }
-        
+
         // Rename to match controller expectations
         public string? LiveUrl { get; set; }
-        
+
         public string? ImageUrl { get; set; }
-        
+
         public List<string>? Challenges { get; set; }
-        
+
         // Azure Best Practice: Add fields for sorting and filtering
         public bool IsFeatured { get; set; } = false;
-        
+
         public int DisplayOrder { get; set; } = 0;
-        
+
         // For backward compatibility
         [NotMapped]
         public string? Name
         {
             get => Title;
             set => Title = value ?? string.Empty;
         }
-        
+
         [NotMapped]
         public string? ProjectUrl
         {
             get => LiveUrl;
             set => LiveUrl = value;
         }
-        
+
         [NotMapped]
         public string? GithubUrl
         {
             get => GitHubUrl;
             set => GitHubUrl = value;
         }
+        
+        public int ViewCount { get; set; }
path: api/api.ApiService/Models/Project.cs
user: login: amazon-q-developer[bot]
body: Sorry, I'm not able to suggest a fix for this review finding. 

Request ID : 2c8ac122-72dc-4454-97ed-53db0c0a3ec4
created_at: 2025-05-21T14:05:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: The fix addresses the lack of error handling in the GetAllAsync method by implementing a try-catch block. This modification helps to catch potential exceptions during the database query operation, allowing for proper error handling and logging. The fix includes a TODO comment to implement proper logging, as the current implementation uses Console.WriteLine for demonstration purposes. This change improves the robustness of the method by handling potential database-related errors gracefully.
```suggestion
public async Task<List<ProjectDTO>> GetAllAsync()
        {
            try
            {
                var projects = await _context.Projects.ToListAsync();
                return projects.Select(p => new ProjectDTO
                {
                    Id = p.Id,
                    Title = p.Title,
                    Description = p.Description,
                    ImageUrl = p.ImageUrl,
                    GitHubUrl = p.GitHubUrl,
                    LiveUrl = p.LiveUrl,
                    Created = p.Created,
                    Technologies = p.Technologies,
                    Challenges = p.Challenges ?? new List<string>(),
                    ViewCount = p.ViewCount
                }).ToList();
            }
            catch (Exception ex)
            {
                // TODO: Implement proper logging
                Console.WriteLine($"Error in GetAllAsync: {ex.Message}");
                throw;
            }
        }

        public async Task<ProjectDTO?> GetByIdAsync(int id)
```

created_at: 2025-05-21T14:05:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: The fix replaces FindAsync with FirstOrDefaultAsync and uses a lambda expression to create a parameterized query, which prevents NoSQL injection by ensuring the 'id' parameter is treated as a value and not part of the query structure.
```suggestion
public async Task<ProjectDTO?> GetByIdAsync(int id)
        {
            // Using parameterized query to prevent NoSQL injection
            var project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == id);
            if (project == null) return null;

            return new ProjectDTO
```

created_at: 2025-05-21T14:05:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
+            if (project == null) return null;
+
+            return new ProjectDTO
+            {
+                Id = project.Id,
+                Title = project.Title,
+                Description = project.Description,
+                ImageUrl = project.ImageUrl,
+                GitHubUrl = project.GitHubUrl,
+                LiveUrl = project.LiveUrl,
+                Created = project.Created,
+                Technologies = project.Technologies,
+                Challenges = project.Challenges ?? new List<string>(),
+                ViewCount = project.ViewCount
+            };
+        }
+
+        public async Task IncrementViewCountAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: The fix involves using the FindAsync method of DbSet<T>, which is a safe way to query for a single entity by its primary key. This method is not susceptible to SQL injection as it uses parameterized queries internally.
```suggestion
public async Task IncrementViewCountAsync(int id)
        {
            // using System.Data.Entity; // Import EntityFramework for DbSet<T>
            var project = await _context.Projects.FindAsync(id);
            if (project != null)
            {
```

created_at: 2025-05-21T14:05:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,6 +186,21 @@ public async Task<IActionResult> DeleteProject(int id)
         return NoContent();
     }
     
+    [HttpPost("{slug}/increment-view")]
+    public async Task<IActionResult> IncrementView(string slug)
+    {
+        var project = await _context.Projects.FirstOrDefaultAsync(p => p.Slug == slug);
path: api/api.ApiService/Controllers/ProjectsController.cs
user: login: amazon-q-developer[bot]
body: The fix addresses the comment by replacing the two separate database operations (fetching the project and updating the ViewCount) with a single database operation using ExecuteUpdateAsync. This approach is more efficient as it performs the increment directly in the database without fetching the entire project entity. The fix also handles the case where the project is not found by checking the number of affected rows.
```suggestion
[HttpPost("{slug}/increment-view")]
    public async Task<IActionResult> IncrementView(string slug)
    {
        var affected = await _context.Projects
            .Where(p => p.Slug == slug)
            .ExecuteUpdateAsync(p => p.SetProperty(x => x.ViewCount, x => x.ViewCount + 1));

        if (affected == 0)
        {
            return NotFound();
        }

        return NoContent();
    }
```

created_at: 2025-05-21T14:05:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: The fix addresses the comment by adding logging to the GetAllAsync method. We've imported the Microsoft.Extensions.Logging namespace, added an ILogger<ProjectService> field, updated the constructor to accept the logger, and added logging statements to track method entry, exit, and potential errors. This improves the ability to monitor and debug the method's execution.
```suggestion
using api.ApiService.Data;
using api.ApiService.Models;
using api.ApiService.DTOs;
using Microsoft.Extensions.Logging; // Import for logging

namespace api.ApiService.Services
{
    public class ProjectService
    {
        private readonly PortfolioContext _context;
        private readonly ILogger<ProjectService> _logger;

        public ProjectService(PortfolioContext context, ILogger<ProjectService> logger)
        {
            _context = context;
            _logger = logger;
        }

        public async Task<List<ProjectDTO>> GetAllAsync()
        {
            _logger.LogInformation("Entering GetAllAsync method");
            try
            {
                var projects = await _context.Projects.ToListAsync();
                var projectDTOs = projects.Select(p => new ProjectDTO
                {
                    Id = p.Id,
                    Title = p.Title,
                    Description = p.Description,
                    ImageUrl = p.ImageUrl,
                    GitHubUrl = p.GitHubUrl,
                    LiveUrl = p.LiveUrl,
                    Created = p.Created,
                    Technologies = p.Technologies,
                    Challenges = p.Challenges ?? new List<string>(),
                    ViewCount = p.ViewCount
                }).ToList();
                _logger.LogInformation($"GetAllAsync method completed. Retrieved {projectDTOs.Count} projects");
                return projectDTOs;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred in GetAllAsync method");
                throw;
            }
        }

        public async Task<ProjectDTO?> GetByIdAsync(int id)
```

created_at: 2025-05-21T14:05:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,7 +6,23 @@ function normalizeSlug(title: string) {
   return title?.toLowerCase().replace(/\s+/g, '-');
 }
 
-export default function ProjectCard({ project, onEdit }) {
+interface Project {
+  id?: string;
path: src/app/projects/ProjectCard.tsx
user: login: amazon-q-developer[bot]
body: The fix addresses the issue of multiple optional ID fields in the Project interface by removing the '_id' field and making the 'id' field required. This simplifies the interface and ensures consistent usage of a single ID field throughout the application. The slug generation logic in the ProjectCard component has also been updated to reflect this change, removing the reference to '_id'.
```suggestion
import Image from "next/image";
import { useRouter } from "next/navigation";

// Helper to generate a slug from the project title
function normalizeSlug(title: string) {
  return title?.toLowerCase().replace(/\s+/g, '-');
}

interface Project {
  id: string;
  slug?: string;
  title: string;
  description?: string;
  imageUrl?: string;
  image?: string;
  technologies?: string[];
  viewCount?: number;
}

interface ProjectCardProps {
  project: Project;
}

export default function ProjectCard({ project }: ProjectCardProps) {
  const router = useRouter();
  const slug =
    project.slug ||
    (project.title ? normalizeSlug(project.title) : project.id || '');

  return (
```

created_at: 2025-05-21T14:05:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using api.ApiService.Data;
+using api.ApiService.Models;
+using api.ApiService.DTOs;
+
+namespace api.ApiService.Services
+{
+    public class ProjectService
+    {
+        private readonly PortfolioContext _context;
+
+        public ProjectService(PortfolioContext context)
+        {
+            _context = context;
+        }
+
+        public async Task<List<ProjectDTO>> GetAllAsync()
+        {
+            var projects = await _context.Projects.ToListAsync();
+            return projects.Select(p => new ProjectDTO
+            {
+                Id = p.Id,
+                Title = p.Title,
+                Description = p.Description,
+                ImageUrl = p.ImageUrl,
+                GitHubUrl = p.GitHubUrl,
+                LiveUrl = p.LiveUrl,
+                Created = p.Created,
+                Technologies = p.Technologies,
+                Challenges = p.Challenges ?? new List<string>(),
+                ViewCount = p.ViewCount
+            }).ToList();
+        }
+
+        public async Task<ProjectDTO?> GetByIdAsync(int id)
+        {
+            var project = await _context.Projects.FindAsync(id);
+            if (project == null) return null;
+
+            return new ProjectDTO
path: api/api.ApiService/Services/ProjectService.cs
user: login: amazon-q-developer[bot]
body: The fix addresses the comment by suggesting the use of AutoMapper for entity-to-DTO mapping. A TODO comment is added to implement the mapping using AutoMapper, and a placeholder method `MapProjectToDTO` is used. This approach centralizes the mapping logic, reducing the risk of inconsistencies and simplifying future changes to the mapping process. However, the fix is incomplete as it requires the implementation of the AutoMapper configuration and the `MapProjectToDTO` method in the main code.
```suggestion
var project = await _context.Projects.FindAsync(id);
            if (project == null) return null;

            // TODO: Implement mapping using AutoMapper
            return MapProjectToDTO(project);
        }

        public async Task IncrementViewCountAsync(int id)
```

created_at: 2025-05-21T14:05:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,14 +14,26 @@ interface ProjectDetailProps {
     gitHubUrl?: string;
     liveUrl?: string;
     challenges?: string[];
-    slug?: string; // Make sure slug is available
+    slug?: string;
+    viewCount?: number; // <-- Add this line
   };
 }
 
 export default function ProjectDetail({ project }: ProjectDetailProps) {
   const router = useRouter();
   const slug = project.slug || project.title?.toLowerCase().replace(/\s+/g, '-');
 
+  useEffect(() => {
path: src/app/projects/ProjectDetail.tsx
user: login: amazon-q-developer[bot]
body: Sorry, I'm not able to suggest a fix for this review finding. 

Request ID : 6f00b4ba-6c34-4981-81df-e3ad9921df60
created_at: 2025-05-21T14:05:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: amazon-q-developer[bot]
created_at: 2025-05-21T14:02:07Z
body: :hourglass_flowing_sand: I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done
author_association: CONTRIBUTOR
user: login: vercel[bot]
created_at: 2025-05-21T14:02:07Z
body: [vc]: #DeIk1PWQT1frq0aqW5IsKZwiXSEi4Zmsxz1z7oWYR3k=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJteS1wb3J0Zm9saW8tbWVubm8iLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9tZW5uby1kcmVzY2hlcnMtcHJvamVjdHMvbXktcG9ydGZvbGlvLW1lbm5vLzEzR0tpTGJaejNGTTY0MnBzN3pCbVVGTUw2dHciLCJwcmV2aWV3VXJsIjoiIiwibmV4dENvbW1pdFN0YXR1cyI6IkZBSUxFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **my-portfolio-menno** | ❌ Failed ([Inspect](https://vercel.com/menno-dreschers-projects/my-portfolio-menno/13GKiLbZz3FM642ps7zBmUFML6tw)) |  |  | May 21, 2025 2:13pm |


author_association: NONE
user: login: atlassian[bot]
created_at: 2025-05-21T14:04:21Z
body: Code Reviewer has processed this PR and found no major risks :white_check_mark:
author_association: NONE
user: login: amazon-q-developer[bot]
created_at: 2025-05-21T14:04:41Z
body: :white_check_mark: I finished the code review, and left comments with the issues I found. I will now generate code fix suggestions.
author_association: CONTRIBUTOR