feat(backend): Replace Node.js API with .NET 8

This commit completes the migration of the backend service from a Node.js/Express application to an ASP.NET Core 8 Minimal API.

- Re-implemented all API endpoints in C# for improved performance and type safety.
- Updated the Gitea Actions workflow to use the `setup-dotnet` action, `dotnet publish` for building, and now caches NuGet packages.
- Modified the deployment to create an `appsettings.Production.json` file from Gitea secrets instead of a `.env` file.
- Updated the PM2 ecosystem configuration to run the application using the `dotnet` interpreter.
This commit is contained in:
2025-10-29 19:14:20 -03:00
parent f14e298c46
commit 40fd792be1
30 changed files with 1017 additions and 1077 deletions

View File

@@ -0,0 +1,31 @@

using JoaoLoureiro.Portfolio.Infrastructure.Settings;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
namespace JoaoLoureiro.Portfolio.Infrastructure.HealthCheck;
public class SmtpHealthCheck(IOptions<SmtpSettings> smtpSettings) : IHealthCheck
{
private readonly SmtpSettings _smtpSettings = smtpSettings.Value;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
using var client = new SmtpClient();
try
{
await client.ConnectAsync(_smtpSettings.Host, _smtpSettings.Port, SecureSocketOptions.StartTlsWhenAvailable, cancellationToken);
await client.AuthenticateAsync(_smtpSettings.User, _smtpSettings.Pass, cancellationToken);
await client.DisconnectAsync(true, cancellationToken);
return HealthCheckResult.Healthy("SMTP server is responding correctly.");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Failed to connect or authenticate with SMTP server.", exception: ex);
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.System" Version="9.0.0" />
<PackageReference Include="MailKit" Version="4.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\JoaoLoureiro.Portfolio.Application\JoaoLoureiro.Portfolio.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,44 @@
using JoaoLoureiro.Portfolio.Core.Interfaces;
using JoaoLoureiro.Portfolio.Core.Models;
using JoaoLoureiro.Portfolio.Infrastructure.Settings;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MimeKit;
namespace JoaoLoureiro.Portfolio.Infrastructure.Services;
public class SmtpEmailSender(IOptions<SmtpSettings> smtpSettings, ILogger<SmtpEmailSender> logger) : IEmailSender
{
private readonly SmtpSettings _smtpSettings = smtpSettings.Value;
private readonly ILogger<SmtpEmailSender> _logger = logger;
public async Task SendEmailAsync(EmailToSend email, CancellationToken cancellationToken = default)
{
var mimeMessage = new MimeMessage();
var fromAddress = _smtpSettings.FromEmail ?? _smtpSettings.User;
mimeMessage.From.Add(new MailboxAddress(email.SenderName, fromAddress));
mimeMessage.To.Add(MailboxAddress.Parse(_smtpSettings.ReceivingEmail));
mimeMessage.ReplyTo.Add(MailboxAddress.Parse(email.ReplyToEmail));
mimeMessage.Subject = $"New Portfolio Contact: {email.SenderName}";
var builder = new BodyBuilder
{
TextBody = $"Name: {email.SenderName}\nEmail: {email.ReplyToEmail}\nMessage: {email.MessageBody}",
HtmlBody = $@"<p><strong>Name:</strong> {email.SenderName}</p>
<p><strong>Email:</strong> <a href=""mailto:{email.ReplyToEmail}"">{email.ReplyToEmail}</a></p>
<p><strong>Message:</strong></p>
<p>{email.MessageBody.Replace("\n", "<br>")}</p>"
};
mimeMessage.Body = builder.ToMessageBody();
using var client = new SmtpClient();
await client.ConnectAsync(_smtpSettings.Host, _smtpSettings.Port, SecureSocketOptions.StartTlsWhenAvailable, cancellationToken);
await client.AuthenticateAsync(_smtpSettings.User, _smtpSettings.Pass, cancellationToken);
await client.SendAsync(mimeMessage, cancellationToken);
_logger.LogInformation("Email sent successfully to {Recipient}", _smtpSettings.ReceivingEmail);
}
}

View File

@@ -0,0 +1,11 @@
namespace JoaoLoureiro.Portfolio.Infrastructure.Settings;
public class SmtpSettings
{
public required string Host { get; set; }
public int Port { get; set; }
public required string User { get; set; }
public required string Pass { get; set; }
public string? FromEmail { get; set; }
public required string ReceivingEmail { get; set; }
}