initial commit

This commit is contained in:
2026-07-21 14:28:43 +07:00
commit 01107db6fd
995 changed files with 75124 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
using Indotalent.Data.Entities;
using Indotalent.Infrastructure.Email.Mailgun;
using Indotalent.Infrastructure.Email.Mailjet;
using Indotalent.Infrastructure.Email.SendGrid;
using Indotalent.Infrastructure.Email.Smtp;
using Microsoft.AspNetCore.Identity;
namespace Indotalent.Infrastructure.Email;
public static class DI
{
public static IServiceCollection AddEmailService(this IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<EmailService>();
var emailSettings = configuration.GetSection("EmailSettings").Get<EmailSettingsModel>() ?? new EmailSettingsModel();
if (emailSettings.SendGrid?.IsUsed == true)
{
services.AddHttpClient<IEmailSender, SendGridEmailSender>();
}
else if (emailSettings.Mailgun?.IsUsed == true)
{
services.AddHttpClient<IEmailSender, MailgunEmailSender>();
}
else if (emailSettings.Mailjet?.IsUsed == true)
{
services.AddHttpClient<IEmailSender, MailjetEmailSender>();
}
else if (emailSettings.Smtp?.IsUsed == true)
{
services.AddTransient<IEmailSender, SmtpEmailSender>();
}
else
{
services.AddScoped<IEmailSender, DefaultEmailSender>();
}
services.AddTransient<IEmailSender<ApplicationUser>, IdentityEmailManager<ApplicationUser>>();
return services;
}
}
@@ -0,0 +1,21 @@
namespace Indotalent.Infrastructure.Email;
public class DefaultEmailSender : IEmailSender
{
private readonly ILogger<DefaultEmailSender> _logger;
public DefaultEmailSender(ILogger<DefaultEmailSender> logger)
{
_logger = logger;
}
public Task SendEmailAsync(string email, string subject, string htmlMessage)
{
_logger.LogWarning("Email sending requested but NO active email provider found in EmailSettings.");
_logger.LogInformation("To: {Email}", email);
_logger.LogInformation("Subject: {Subject}", subject);
_logger.LogInformation("Content: {Message}", htmlMessage);
return Task.CompletedTask;
}
}
+10
View File
@@ -0,0 +1,10 @@
using Microsoft.Extensions.Options;
namespace Indotalent.Infrastructure.Email;
public class EmailService(IOptions<EmailSettingsModel> options)
{
private readonly EmailSettingsModel _settings = options.Value;
public EmailSettingsModel GetConfiguration() => _settings;
}
@@ -0,0 +1,14 @@
using Indotalent.Infrastructure.Email.Mailgun;
using Indotalent.Infrastructure.Email.Mailjet;
using Indotalent.Infrastructure.Email.SendGrid;
using Indotalent.Infrastructure.Email.Smtp;
namespace Indotalent.Infrastructure.Email;
public class EmailSettingsModel
{
public SendGridSettingsModel SendGrid { get; set; } = new();
public MailgunSettingsModel Mailgun { get; set; } = new();
public MailjetSettingsModel Mailjet { get; set; } = new();
public SmtpSettingsModel Smtp { get; set; } = new();
}
+6
View File
@@ -0,0 +1,6 @@
namespace Indotalent.Infrastructure.Email;
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string htmlMessage);
}
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Identity;
namespace Indotalent.Infrastructure.Email;
public class IdentityEmailManager<TUser> : IEmailSender<TUser> where TUser : class
{
private readonly IEmailSender _emailSender;
public IdentityEmailManager(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public Task SendConfirmationLinkAsync(TUser user, string email, string confirmationLink)
{
var message = confirmationLink.Contains("<")
? confirmationLink
: $"Please confirm your account by clicking this link: <a href='{confirmationLink}'>Confirm Account</a>";
return _emailSender.SendEmailAsync(email, "Account Confirmation", message);
}
public Task SendPasswordResetLinkAsync(TUser user, string email, string resetLink)
{
var message = resetLink.Contains("<")
? resetLink
: $"To reset your password, please click the following link: <a href='{resetLink}'>Reset Password</a>";
return _emailSender.SendEmailAsync(email, "Password Reset Request", message);
}
public Task SendPasswordResetCodeAsync(TUser user, string email, string resetCode) =>
_emailSender.SendEmailAsync(email, "Your Password Reset Code",
$"Your password reset security code is: <b>{resetCode}</b>. Please enter this code to proceed with the reset process.");
}
@@ -0,0 +1,44 @@
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Text;
namespace Indotalent.Infrastructure.Email.Mailgun;
public class MailgunEmailSender : IEmailSender
{
private readonly MailgunSettingsModel _settings;
private readonly HttpClient _httpClient;
public MailgunEmailSender(IOptions<EmailSettingsModel> emailOptions, HttpClient httpClient)
{
_settings = emailOptions.Value.Mailgun;
_httpClient = httpClient;
}
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
if (string.IsNullOrEmpty(_settings.ApiKey) || string.IsNullOrEmpty(_settings.Domain))
{
throw new Exception("Mailgun configuration is missing ApiKey or Domain.");
}
var authToken = Encoding.ASCII.GetBytes($"api:{_settings.ApiKey}");
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("from", _settings.FromEmail),
new KeyValuePair<string, string>("to", email),
new KeyValuePair<string, string>("subject", subject),
new KeyValuePair<string, string>("html", htmlMessage)
});
var response = await _httpClient.PostAsync($"https://api.mailgun.net/v3/{_settings.Domain}/messages", formContent);
if (!response.IsSuccessStatusCode)
{
var errorBody = await response.Content.ReadAsStringAsync();
throw new Exception($"Failed to send email via Mailgun. Status: {response.StatusCode}, Error: {errorBody}");
}
}
}
@@ -0,0 +1,9 @@
namespace Indotalent.Infrastructure.Email.Mailgun;
public class MailgunSettingsModel
{
public bool IsUsed { get; set; }
public string ApiKey { get; set; } = string.Empty;
public string Domain { get; set; } = string.Empty;
public string FromEmail { get; set; } = string.Empty;
}
@@ -0,0 +1,52 @@
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace Indotalent.Infrastructure.Email.Mailjet;
public class MailjetEmailSender : IEmailSender
{
private readonly MailjetSettingsModel _settings;
private readonly HttpClient _httpClient;
public MailjetEmailSender(IOptions<EmailSettingsModel> emailOptions, HttpClient httpClient)
{
_settings = emailOptions.Value.Mailjet;
_httpClient = httpClient;
}
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
if (string.IsNullOrEmpty(_settings.ApiKey) || string.IsNullOrEmpty(_settings.ApiSecret))
{
throw new Exception("Mailjet configuration is missing ApiKey or ApiSecret.");
}
var authToken = Encoding.UTF8.GetBytes($"{_settings.ApiKey}:{_settings.ApiSecret}");
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
var payload = new
{
Messages = new[]
{
new
{
From = new { Email = _settings.FromEmail, Name = "Mailjet System" },
To = new[] { new { Email = email } },
Subject = subject,
HTMLPart = htmlMessage
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("https://api.mailjet.com/v3.1/send", content);
if (!response.IsSuccessStatusCode)
{
var errorBody = await response.Content.ReadAsStringAsync();
throw new Exception($"Failed to send email via Mailjet. Status: {response.StatusCode}, Error: {errorBody}");
}
}
}
@@ -0,0 +1,9 @@
namespace Indotalent.Infrastructure.Email.Mailjet;
public class MailjetSettingsModel
{
public bool IsUsed { get; set; }
public string ApiKey { get; set; } = string.Empty;
public string ApiSecret { get; set; } = string.Empty;
public string FromEmail { get; set; } = string.Empty;
}
@@ -0,0 +1,58 @@
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace Indotalent.Infrastructure.Email.SendGrid;
public class SendGridEmailSender : IEmailSender
{
private readonly SendGridSettingsModel _settings;
private readonly HttpClient _httpClient;
public SendGridEmailSender(IOptions<EmailSettingsModel> emailOptions, HttpClient httpClient)
{
_settings = emailOptions.Value.SendGrid;
_httpClient = httpClient;
}
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
if (string.IsNullOrEmpty(_settings.ApiKey))
{
throw new Exception("SendGrid configuration is missing ApiKey.");
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _settings.ApiKey);
var payload = new
{
personalizations = new[]
{
new
{
to = new[] { new { email = email } }
}
},
from = new { email = _settings.FromEmail, name = "SendGrid System" },
subject = subject,
content = new[]
{
new
{
type = "text/html",
value = htmlMessage
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("https://api.sendgrid.com/v3/mail/send", content);
if (!response.IsSuccessStatusCode)
{
var errorBody = await response.Content.ReadAsStringAsync();
throw new Exception($"Failed to send email via SendGrid. Status: {response.StatusCode}, Error: {errorBody}");
}
}
}
@@ -0,0 +1,8 @@
namespace Indotalent.Infrastructure.Email.SendGrid;
public class SendGridSettingsModel
{
public bool IsUsed { get; set; }
public string ApiKey { get; set; } = string.Empty;
public string FromEmail { get; set; } = string.Empty;
}
@@ -0,0 +1,54 @@
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Options;
using MimeKit;
namespace Indotalent.Infrastructure.Email.Smtp;
public class SmtpEmailSender : IEmailSender
{
private readonly SmtpSettingsModel _settings;
public SmtpEmailSender(IOptions<EmailSettingsModel> emailOptions)
{
_settings = emailOptions.Value.Smtp;
}
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(_settings.FromName, _settings.FromAddress));
message.To.Add(MailboxAddress.Parse(email));
message.Subject = subject;
var bodyBuilder = new BodyBuilder
{
HtmlBody = htmlMessage
};
message.Body = bodyBuilder.ToMessageBody();
using var client = new SmtpClient();
try
{
await client.ConnectAsync(_settings.Host, _settings.Port, SecureSocketOptions.Auto);
if (!string.IsNullOrEmpty(_settings.UserName))
{
await client.AuthenticateAsync(_settings.UserName, _settings.Password);
}
await client.SendAsync(message);
}
catch (Exception ex)
{
throw new Exception($"Failed to send email via SMTP to {email} via {_settings.Host}. Error: {ex.Message}", ex);
}
finally
{
await client.DisconnectAsync(true);
}
}
}
@@ -0,0 +1,12 @@
namespace Indotalent.Infrastructure.Email.Smtp;
public class SmtpSettingsModel
{
public bool IsUsed { get; set; }
public string Host { get; set; } = string.Empty;
public int Port { get; set; }
public string UserName { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string FromAddress { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
}