Compare commits

..

8 Commits

Author SHA1 Message Date
rajukottedi 62fc6c1a49 Merge pull request 'Improve error handling and logging for mail and OTP endpoints' (#7) from feature/portfolio-admin-auth into dev
Reviewed-on: #7
2026-02-16 11:54:21 +05:30
rajukottedi 4b968099ad Improve error handling and logging for mail and OTP endpoints
Updated AuthController and CvController to return exception messages in 500 responses and enhanced logging. MailService now throws descriptive exceptions on email send failures, allowing upstream error reporting and preserving failed messages.
2026-02-16 11:44:31 +05:30
rajukottedi 90d04d7a42 Merge pull request 'Improve URL config parsing and mail exception handling' (#5) from feature/portfolio-admin-auth into dev
Reviewed-on: #5
2026-02-16 11:19:29 +05:30
rajukottedi 2d603e4fc6 Improve URL config parsing and mail exception handling
Refactor Program.cs to robustly parse and validate URLs from config, logging a warning if none are valid. Enhance MailService exception handling to log full exceptions and avoid interrupting OTP generation by swallowing mail send errors.
2026-02-16 11:18:46 +05:30
rajukottedi a23953fe11 Merge pull request 'Support encrypted cache conn strings & update dependencies' (#3) from feature/portfolio-admin-auth into dev
Reviewed-on: #3
2026-02-15 14:23:56 +05:30
rajukottedi 2533b5298f Support encrypted cache conn strings & update dependencies
Moved DecryptConnectionString to KBR.Shared.Extensions for reuse and improved configuration access. Added Encryption property to CacheOptions and appsettings for conditional decryption of cache connection strings. Updated ServiceCollectionExtensions to decrypt SQL Server cache connection strings when needed. Upgraded NuGet packages across projects to latest .NET 8/10.0.x versions. Included minor code cleanups and OpenApiSecurityScheme improvements.
2026-02-15 14:22:22 +05:30
rajukottedi 6ac6b1e08e Merge pull request 'feature/portfolio-admin-auth' (#2) from feature/portfolio-admin-auth into dev
Reviewed-on: #2
2026-02-15 13:27:58 +05:30
rajukottedi 4ebd93d39b Update projects to .NET 10.0 and add Program doc entry
Upgraded target frameworks to .NET 10.0 across multiple projects for latest compatibility. Added documentation for the auto-generated Program class in PortBlog.API.xml. No other functional changes.
2026-02-15 13:22:48 +05:30
15 changed files with 74 additions and 42 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPublishable>false</IsPublishable> <IsPublishable>false</IsPublishable>
+1 -1
View File
@@ -59,7 +59,7 @@ namespace PortBlog.API.Controllers
catch (Exception ex) catch (Exception ex)
{ {
logger.LogCritical(ex, "Exception while sending OTP for {ToEmail}.", messageSendDto.ToEmail); logger.LogCritical(ex, "Exception while sending OTP for {ToEmail}.", messageSendDto.ToEmail);
return StatusCode(500, "A problem happened while handling your request."); return StatusCode(500, ex.Message);
} }
} }
+2 -2
View File
@@ -262,8 +262,8 @@ namespace PortBlog.API.Controllers
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogCritical($"Exception while sending message from {message.Name}.", ex); _logger.LogCritical(ex, "Exception while sending message from {Name}.", message.Name);
return StatusCode(500, "A problem happened while handling your request."); return StatusCode(500, ex.Message);
} }
} }
} }
+4 -4
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
@@ -10,10 +10,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.1" /> <PackageReference Include="Asp.Versioning.Mvc" Version="8.1.1" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" /> <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.1" />
<PackageReference Include="AutoMapper" Version="15.0.1" /> <PackageReference Include="AutoMapper" Version="16.0.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.10" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9">
+5
View File
@@ -696,5 +696,10 @@
<param name="model">The model to use when rendering the view.</param> <param name="model">The model to use when rendering the view.</param>
<returns>A task that represents the asynchronous operation. The task result contains the rendered view as a string.</returns> <returns>A task that represents the asynchronous operation. The task result contains the rendered view as a string.</returns>
</member> </member>
<member name="T:Program">
<summary>
Auto-generated public partial Program class for top-level statement apps.
</summary>
</member>
</members> </members>
</doc> </doc>
+18 -7
View File
@@ -12,7 +12,7 @@ using PortBlog.API.Middleware;
using Serilog; using Serilog;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using KBR.Shared.Extensions;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -33,13 +33,25 @@ Log.Logger = loggerConfiguration
.WriteTo.File("logs/portblog.txt", rollingInterval: RollingInterval.Day) .WriteTo.File("logs/portblog.txt", rollingInterval: RollingInterval.Day)
.CreateLogger(); .CreateLogger();
var urls = builder.Configuration.GetSection("Urls"); var urlsSection = builder.Configuration.GetSection("Urls");
var urlsValue = urlsSection.Value;
if (!string.IsNullOrEmpty(urls.Value)) if (!string.IsNullOrWhiteSpace(urlsValue))
{ {
var allowedUrlsToUse = urls.Value.Split(','); var allowedUrlsToUse = urlsValue
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(u => Uri.IsWellFormedUriString(u, UriKind.Absolute))
.ToArray();
if (allowedUrlsToUse.Length > 0)
{
builder.WebHost.UseUrls(allowedUrlsToUse); builder.WebHost.UseUrls(allowedUrlsToUse);
}
else
{
// optionally log or throw depending on desired behavior
Log.Warning("Configured 'Urls' section was present but no valid URLs were found.");
}
} }
var allowedCorsOrigins = builder.Configuration.GetSection("AllowedCorsOrigins"); var allowedCorsOrigins = builder.Configuration.GetSection("AllowedCorsOrigins");
@@ -131,14 +143,13 @@ builder.Services.AddSwaggerGen(c =>
Scheme = "ApiKeyScheme" Scheme = "ApiKeyScheme"
}); });
var key = new OpenApiSecurityScheme() var key = new OpenApiSecurityScheme
{ {
Reference = new OpenApiReference Reference = new OpenApiReference
{ {
Type = ReferenceType.SecurityScheme, Type = ReferenceType.SecurityScheme,
Id = "ApiKey" Id = "ApiKey"
}, }
In = ParameterLocation.Header
}; };
// JWT Bearer Security Definition // JWT Bearer Security Definition
+5 -7
View File
@@ -67,16 +67,14 @@ namespace PortBlog.API.Services
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogCritical( // Log full exception and persist failed message.
"Exception while sending mail from {FromEmail} ({Name}): {Exception}", logger.LogCritical(ex, "Exception while sending mail from {FromEmail} ({Name}).", messageSendDto.FromEmail, messageSendDto.Name);
messageSendDto.FromEmail,
messageSendDto.Name,
ex.Message
);
messageSendDto.SentStatus = (int)MailConstants.MailStatus.Failed; messageSendDto.SentStatus = (int)MailConstants.MailStatus.Failed;
mailRepository.AddMessage(messageEntity); mailRepository.AddMessage(messageEntity);
await mailRepository.SaveChangesAsync(); await mailRepository.SaveChangesAsync();
throw new Exception();
// Throw a descriptive exception so callers can return an error message to the client.
throw new InvalidOperationException($"Failed to send email to '{messageSendDto.ToEmail}'. See inner exception for details.", ex);
} }
} }
} }
@@ -6,6 +6,7 @@
}, },
"Cache": { "Cache": {
"ConnectionString": "SERVER=192.168.0.197; DATABASE=cv_blog; UID=PortBlogDevUser; PWD=p@$$w0rd1234;Allow User Variables=true;", "ConnectionString": "SERVER=192.168.0.197; DATABASE=cv_blog; UID=PortBlogDevUser; PWD=p@$$w0rd1234;Allow User Variables=true;",
"Encryption": "false",
"Provider": "SqlServer" "Provider": "SqlServer"
}, },
"Jwt": { "Jwt": {
+9 -3
View File
@@ -8,10 +8,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" /> <PackageReference Include="coverlet.collector" Version="8.0.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" /> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,5 +1,6 @@
using KBR.Cache.Constants; using KBR.Cache.Constants;
using KBR.Cache.Options; using KBR.Cache.Options;
using KBR.Shared.Extensions;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -20,9 +21,14 @@ namespace KBR.Cache.Extensions
} }
else if (cacheOptions.Provider == CacheProviders.SqlServer) else if (cacheOptions.Provider == CacheProviders.SqlServer)
{ {
var connectionString = cacheOptions.ConnectionString;
if ((bool)cacheOptions.Encryption)
{
connectionString = configuration.DecryptConnectionString(connectionString);
}
services.AddDistributedMySqlCache(options => services.AddDistributedMySqlCache(options =>
{ {
options.ConnectionString = cacheOptions.ConnectionString; options.ConnectionString = connectionString;
options.TableName = "Cache"; options.TableName = "Cache";
}); });
} }
+9 -9
View File
@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3" />
<PackageReference Include="Pomelo.Extensions.Caching.MySql" Version="2.2.1" /> <PackageReference Include="Pomelo.Extensions.Caching.MySql" Version="2.2.1" />
</ItemGroup> </ItemGroup>
+2
View File
@@ -4,6 +4,8 @@
{ {
public string Provider { get; set; } = null!; public string Provider { get; set; } = null!;
public bool? Encryption { get; set; } = false;
public string? ConnectionString { get; set; } public string? ConnectionString { get; set; }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -1,12 +1,14 @@
using System.Security.Cryptography; using Microsoft.Extensions.Configuration;
using System.Security.Cryptography;
namespace PortBlog.API.Extensions namespace KBR.Shared.Extensions
{ {
public static class ConfigurationExtensions public static class ConfigurationExtensions
{ {
public static string DecryptConnectionString(this IConfiguration configuration, string encryptedConnectionString) public static string DecryptConnectionString(this IConfiguration configuration, string encryptedConnectionString)
{ {
string keyBase64 = configuration.GetValue<string>("ConnectionStrings:Key").ToString(); // Fix: Use GetSection and Value instead of GetValue (since GetValue is not available on IConfiguration)
string keyBase64 = configuration.GetSection("ConnectionStrings:Key").Value;
string vectorBase64 = encryptedConnectionString.Split(":")[1]; string vectorBase64 = encryptedConnectionString.Split(":")[1];
string cipherText = encryptedConnectionString.Split(":")[0]; string cipherText = encryptedConnectionString.Split(":")[0];
+3 -2
View File
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>