Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions Aquiis.SimpleStart/Application/Services/EmailSettingsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using System.Threading.Tasks;
using Aquiis.SimpleStart.Core.Constants;
using Aquiis.SimpleStart.Core.Entities;
using Aquiis.SimpleStart.Core.Interfaces.Services;
using Aquiis.SimpleStart.Core.Services;
using Aquiis.SimpleStart.Infrastructure.Data;
using Aquiis.SimpleStart.Infrastructure.Services;
using Aquiis.SimpleStart.Shared.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace Aquiis.SimpleStart.Application.Services
{
public class EmailSettingsService : BaseService<OrganizationEmailSettings>
{
private readonly SendGridEmailService _emailService;

public EmailSettingsService(
ApplicationDbContext context,
ILogger<EmailSettingsService> logger,
UserContextService userContext,
IOptions<ApplicationSettings> settings,
SendGridEmailService emailService)
: base(context, logger, userContext, settings)
{
_emailService = emailService;
}

/// <summary>
/// Get email settings for current organization or create default disabled settings
/// </summary>
public async Task<OrganizationEmailSettings> GetOrCreateSettingsAsync()
{
var orgId = await _userContext.GetActiveOrganizationIdAsync();
if (orgId == null)
{
throw new UnauthorizedAccessException("No active organization");
}

var settings = await _dbSet
.FirstOrDefaultAsync(s => s.OrganizationId == orgId && !s.IsDeleted);

if (settings == null)
{
settings = new OrganizationEmailSettings
{
Id = Guid.NewGuid(),
OrganizationId = orgId.Value,
IsEmailEnabled = false,
DailyLimit = 100, // SendGrid free tier default
MonthlyLimit = 40000,
CreatedBy = await _userContext.GetUserIdAsync() ?? string.Empty,
CreatedOn = DateTime.UtcNow
};
await CreateAsync(settings);
}

return settings;
}

/// <summary>
/// Configure SendGrid API key and enable email functionality
/// </summary>
public async Task<OperationResult> UpdateSendGridConfigAsync(
string apiKey,
string fromEmail,
string fromName)
{
// Verify the API key works before saving
if (!await _emailService.VerifyApiKeyAsync(apiKey))
{
return OperationResult.FailureResult(
"Invalid SendGrid API key. Please verify the key has Mail Send permissions.");
}

var settings = await GetOrCreateSettingsAsync();

settings.SendGridApiKeyEncrypted = _emailService.EncryptApiKey(apiKey);
settings.FromEmail = fromEmail;
settings.FromName = fromName;
settings.IsEmailEnabled = true;
settings.IsVerified = true;
settings.LastVerifiedOn = DateTime.UtcNow;
settings.LastError = null;

await UpdateAsync(settings);

return OperationResult.SuccessResult("SendGrid configuration saved successfully");
}

/// <summary>
/// Disable email functionality for organization
/// </summary>
public async Task<OperationResult> DisableEmailAsync()
{
var settings = await GetOrCreateSettingsAsync();
settings.IsEmailEnabled = false;
await UpdateAsync(settings);

return OperationResult.SuccessResult("Email notifications disabled");
}

/// <summary>
/// Re-enable email functionality
/// </summary>
public async Task<OperationResult> EnableEmailAsync()
{
var settings = await GetOrCreateSettingsAsync();

if (string.IsNullOrEmpty(settings.SendGridApiKeyEncrypted))
{
return OperationResult.FailureResult(
"SendGrid API key not configured. Please configure SendGrid first.");
}

settings.IsEmailEnabled = true;
await UpdateAsync(settings);

return OperationResult.SuccessResult("Email notifications enabled");
}

/// <summary>
/// Send a test email to verify configuration
/// </summary>
public async Task<OperationResult> TestEmailConfigurationAsync(string testEmail)
{
try
{
await _emailService.SendEmailAsync(
testEmail,
"Aquiis Email Configuration Test",
"<h2>Configuration Test Successful!</h2>" +
"<p>This is a test email to verify your SendGrid configuration is working correctly.</p>" +
"<p>If you received this email, your email integration is properly configured.</p>");

return OperationResult.SuccessResult("Test email sent successfully! Check your inbox.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Test email failed");
return OperationResult.FailureResult($"Failed to send test email: {ex.Message}");
}
}

/// <summary>
/// Update email sender information
/// </summary>
public async Task<OperationResult> UpdateSenderInfoAsync(string fromEmail, string fromName)
{
var settings = await GetOrCreateSettingsAsync();

settings.FromEmail = fromEmail;
settings.FromName = fromName;

await UpdateAsync(settings);

return OperationResult.SuccessResult("Sender information updated");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

using Aquiis.SimpleStart.Core.Constants;
using Aquiis.SimpleStart.Core.Entities;
using Aquiis.SimpleStart.Core.Interfaces.Services;
using Aquiis.SimpleStart.Core.Services;
using Aquiis.SimpleStart.Infrastructure.Data;
Expand Down
127 changes: 127 additions & 0 deletions Aquiis.SimpleStart/Application/Services/SMSSettingsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Threading.Tasks;
using Aquiis.SimpleStart.Core.Constants;
using Aquiis.SimpleStart.Core.Entities;
using Aquiis.SimpleStart.Core.Services;
using Aquiis.SimpleStart.Infrastructure.Data;
using Aquiis.SimpleStart.Infrastructure.Services;
using Aquiis.SimpleStart.Shared.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Aquiis.SimpleStart.Application.Services
{
public class SMSSettingsService : BaseService<OrganizationSMSSettings>
{
private readonly TwilioSMSService _smsService;

public SMSSettingsService(
ApplicationDbContext context,
ILogger<SMSSettingsService> logger,
UserContextService userContext,
IOptions<ApplicationSettings> settings,
TwilioSMSService smsService)
: base(context, logger, userContext, settings)
{
_smsService = smsService;
}

public async Task<OrganizationSMSSettings> GetOrCreateSettingsAsync()
{
var orgId = await _userContext.GetActiveOrganizationIdAsync();
if (orgId == null)
{
throw new UnauthorizedAccessException("No active organization");
}

var settings = await _dbSet
.FirstOrDefaultAsync(s => s.OrganizationId == orgId && !s.IsDeleted);

if (settings == null)
{
settings = new OrganizationSMSSettings
{
Id = Guid.NewGuid(),
OrganizationId = orgId.Value,
IsSMSEnabled = false,
CostPerSMS = 0.0075m, // Approximate US cost
CreatedBy = await _userContext.GetUserIdAsync() ?? string.Empty,
CreatedOn = DateTime.UtcNow
};
await CreateAsync(settings);
}

return settings;
}

public async Task<OperationResult> UpdateTwilioConfigAsync(
string accountSid,
string authToken,
string phoneNumber)
{
// Verify credentials work before saving
if (!await _smsService.VerifyTwilioCredentialsAsync(accountSid, authToken, phoneNumber))
{
return OperationResult.FailureResult(
"Invalid Twilio credentials or phone number. Please verify your Account SID, Auth Token, and phone number.");
}

var settings = await GetOrCreateSettingsAsync();

settings.TwilioAccountSidEncrypted = _smsService.EncryptAccountSid(accountSid);
settings.TwilioAuthTokenEncrypted = _smsService.EncryptAuthToken(authToken);
settings.TwilioPhoneNumber = phoneNumber;
settings.IsSMSEnabled = true;
settings.IsVerified = true;
settings.LastVerifiedOn = DateTime.UtcNow;
settings.LastError = null;

await UpdateAsync(settings);

return OperationResult.SuccessResult("Twilio configuration saved successfully");
}

public async Task<OperationResult> DisableSMSAsync()
{
var settings = await GetOrCreateSettingsAsync();
settings.IsSMSEnabled = false;
await UpdateAsync(settings);

return OperationResult.SuccessResult("SMS notifications disabled");
}

public async Task<OperationResult> EnableSMSAsync()
{
var settings = await GetOrCreateSettingsAsync();

if (string.IsNullOrEmpty(settings.TwilioAccountSidEncrypted))
{
return OperationResult.FailureResult(
"Twilio credentials not configured. Please configure Twilio first.");
}

settings.IsSMSEnabled = true;
await UpdateAsync(settings);

return OperationResult.SuccessResult("SMS notifications enabled");
}

public async Task<OperationResult> TestSMSConfigurationAsync(string testPhoneNumber)
{
try
{
await _smsService.SendSMSAsync(
testPhoneNumber,
"Aquiis SMS Configuration Test: This message confirms your Twilio integration is working correctly.");

return OperationResult.SuccessResult("Test SMS sent successfully! Check your phone.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Test SMS failed");
return OperationResult.FailureResult($"Failed to send test SMS: {ex.Message}");
}
}
}
}
2 changes: 2 additions & 0 deletions Aquiis.SimpleStart/Aquiis.SimpleStart.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.11" />
<PackageReference Include="SendGrid" Version="9.29.3" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11" />
<PackageReference Include="QuestPDF" Version="2025.7.4" />
<PackageReference Include="Twilio" Version="7.14.0" />
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions Aquiis.SimpleStart/Core/Entities/OperationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Aquiis.SimpleStart.Core.Entities
{
public class OperationResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public List<string> Errors { get; set; } = new();

public static OperationResult SuccessResult(string message = "Operation completed successfully")
{
return new OperationResult { Success = true, Message = message };
}

public static OperationResult FailureResult(string message, List<string>? errors = null)
{
return new OperationResult
{
Success = false,
Message = message,
Errors = errors ?? new List<string>()
};
}
}
}
Loading