Skip to content

Commit 96e866f

Browse files
author
CIS Guru
committed
SendGrid and Twilio setup
1 parent 1d95756 commit 96e866f

24 files changed

Lines changed: 7471 additions & 40 deletions
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Aquiis.SimpleStart.Core.Constants;
4+
using Aquiis.SimpleStart.Core.Entities;
5+
using Aquiis.SimpleStart.Core.Interfaces.Services;
6+
using Aquiis.SimpleStart.Core.Services;
7+
using Aquiis.SimpleStart.Infrastructure.Data;
8+
using Aquiis.SimpleStart.Infrastructure.Services;
9+
using Aquiis.SimpleStart.Shared.Services;
10+
using Microsoft.EntityFrameworkCore;
11+
using Microsoft.Extensions.Logging;
12+
using Microsoft.Extensions.Options;
13+
using SendGrid;
14+
using SendGrid.Helpers.Mail;
15+
16+
namespace Aquiis.SimpleStart.Application.Services
17+
{
18+
public class EmailSettingsService : BaseService<OrganizationEmailSettings>
19+
{
20+
private readonly SendGridEmailService _emailService;
21+
22+
public EmailSettingsService(
23+
ApplicationDbContext context,
24+
ILogger<EmailSettingsService> logger,
25+
UserContextService userContext,
26+
IOptions<ApplicationSettings> settings,
27+
SendGridEmailService emailService)
28+
: base(context, logger, userContext, settings)
29+
{
30+
_emailService = emailService;
31+
}
32+
33+
/// <summary>
34+
/// Get email settings for current organization or create default disabled settings
35+
/// </summary>
36+
public async Task<OrganizationEmailSettings> GetOrCreateSettingsAsync()
37+
{
38+
var orgId = await _userContext.GetActiveOrganizationIdAsync();
39+
if (orgId == null)
40+
{
41+
throw new UnauthorizedAccessException("No active organization");
42+
}
43+
44+
var settings = await _dbSet
45+
.FirstOrDefaultAsync(s => s.OrganizationId == orgId && !s.IsDeleted);
46+
47+
if (settings == null)
48+
{
49+
settings = new OrganizationEmailSettings
50+
{
51+
Id = Guid.NewGuid(),
52+
OrganizationId = orgId.Value,
53+
IsEmailEnabled = false,
54+
DailyLimit = 100, // SendGrid free tier default
55+
MonthlyLimit = 40000,
56+
CreatedBy = await _userContext.GetUserIdAsync() ?? string.Empty,
57+
CreatedOn = DateTime.UtcNow
58+
};
59+
await CreateAsync(settings);
60+
}
61+
62+
return settings;
63+
}
64+
65+
/// <summary>
66+
/// Configure SendGrid API key and enable email functionality
67+
/// </summary>
68+
public async Task<OperationResult> UpdateSendGridConfigAsync(
69+
string apiKey,
70+
string fromEmail,
71+
string fromName)
72+
{
73+
// Verify the API key works before saving
74+
if (!await _emailService.VerifyApiKeyAsync(apiKey))
75+
{
76+
return OperationResult.FailureResult(
77+
"Invalid SendGrid API key. Please verify the key has Mail Send permissions.");
78+
}
79+
80+
var settings = await GetOrCreateSettingsAsync();
81+
82+
settings.SendGridApiKeyEncrypted = _emailService.EncryptApiKey(apiKey);
83+
settings.FromEmail = fromEmail;
84+
settings.FromName = fromName;
85+
settings.IsEmailEnabled = true;
86+
settings.IsVerified = true;
87+
settings.LastVerifiedOn = DateTime.UtcNow;
88+
settings.LastError = null;
89+
90+
await UpdateAsync(settings);
91+
92+
return OperationResult.SuccessResult("SendGrid configuration saved successfully");
93+
}
94+
95+
/// <summary>
96+
/// Disable email functionality for organization
97+
/// </summary>
98+
public async Task<OperationResult> DisableEmailAsync()
99+
{
100+
var settings = await GetOrCreateSettingsAsync();
101+
settings.IsEmailEnabled = false;
102+
await UpdateAsync(settings);
103+
104+
return OperationResult.SuccessResult("Email notifications disabled");
105+
}
106+
107+
/// <summary>
108+
/// Re-enable email functionality
109+
/// </summary>
110+
public async Task<OperationResult> EnableEmailAsync()
111+
{
112+
var settings = await GetOrCreateSettingsAsync();
113+
114+
if (string.IsNullOrEmpty(settings.SendGridApiKeyEncrypted))
115+
{
116+
return OperationResult.FailureResult(
117+
"SendGrid API key not configured. Please configure SendGrid first.");
118+
}
119+
120+
settings.IsEmailEnabled = true;
121+
await UpdateAsync(settings);
122+
123+
return OperationResult.SuccessResult("Email notifications enabled");
124+
}
125+
126+
/// <summary>
127+
/// Send a test email to verify configuration
128+
/// </summary>
129+
public async Task<OperationResult> TestEmailConfigurationAsync(string testEmail)
130+
{
131+
try
132+
{
133+
await _emailService.SendEmailAsync(
134+
testEmail,
135+
"Aquiis Email Configuration Test",
136+
"<h2>Configuration Test Successful!</h2>" +
137+
"<p>This is a test email to verify your SendGrid configuration is working correctly.</p>" +
138+
"<p>If you received this email, your email integration is properly configured.</p>");
139+
140+
return OperationResult.SuccessResult("Test email sent successfully! Check your inbox.");
141+
}
142+
catch (Exception ex)
143+
{
144+
_logger.LogError(ex, "Test email failed");
145+
return OperationResult.FailureResult($"Failed to send test email: {ex.Message}");
146+
}
147+
}
148+
149+
/// <summary>
150+
/// Update email sender information
151+
/// </summary>
152+
public async Task<OperationResult> UpdateSenderInfoAsync(string fromEmail, string fromName)
153+
{
154+
var settings = await GetOrCreateSettingsAsync();
155+
156+
settings.FromEmail = fromEmail;
157+
settings.FromName = fromName;
158+
159+
await UpdateAsync(settings);
160+
161+
return OperationResult.SuccessResult("Sender information updated");
162+
}
163+
}
164+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
2+
using Aquiis.SimpleStart.Core.Constants;
3+
using Aquiis.SimpleStart.Core.Entities;
4+
using Aquiis.SimpleStart.Core.Interfaces.Services;
5+
using Aquiis.SimpleStart.Core.Services;
6+
using Aquiis.SimpleStart.Infrastructure.Data;
7+
using Aquiis.SimpleStart.Shared.Services;
8+
using Microsoft.EntityFrameworkCore;
9+
using Microsoft.Extensions.Options;
10+
11+
namespace Aquiis.SimpleStart.Application.Services
12+
{
13+
public class NotificationService : BaseService<Notification>
14+
{
15+
private readonly IEmailService _emailService;
16+
private readonly ISMSService _smsService;
17+
private new readonly ILogger<NotificationService> _logger;
18+
19+
public NotificationService(
20+
ApplicationDbContext context,
21+
ILogger<NotificationService> logger,
22+
UserContextService userContext,
23+
IOptions<ApplicationSettings> settings,
24+
IEmailService emailService,
25+
ISMSService smsService)
26+
: base(context, logger, userContext, settings)
27+
{
28+
_emailService = emailService;
29+
_smsService = smsService;
30+
_logger = logger;
31+
}
32+
33+
/// <summary>
34+
/// Create and send a notification to a user
35+
/// </summary>
36+
public async Task<Notification> SendNotificationAsync(
37+
string recipientUserId,
38+
string title,
39+
string message,
40+
string type,
41+
string category,
42+
Guid? relatedEntityId = null,
43+
string? relatedEntityType = null)
44+
{
45+
var organizationId = await _userContext.GetActiveOrganizationIdAsync();
46+
47+
// Get user preferences
48+
var preferences = await GetNotificationPreferencesAsync(recipientUserId);
49+
50+
var notification = new Notification
51+
{
52+
Id = Guid.NewGuid(),
53+
OrganizationId = organizationId!.Value,
54+
RecipientUserId = recipientUserId,
55+
Title = title,
56+
Message = message,
57+
Type = type,
58+
Category = category,
59+
RelatedEntityId = relatedEntityId,
60+
RelatedEntityType = relatedEntityType,
61+
SentOn = DateTime.UtcNow,
62+
IsRead = false,
63+
SendInApp = preferences.EnableInAppNotifications,
64+
SendEmail = preferences.EnableEmailNotifications && ShouldSendEmail(category, preferences),
65+
SendSMS = preferences.EnableSMSNotifications && ShouldSendSMS(category, preferences)
66+
};
67+
68+
// Save in-app notification
69+
await CreateAsync(notification);
70+
71+
// Send email if enabled
72+
if (notification.SendEmail && !string.IsNullOrEmpty(preferences.EmailAddress))
73+
{
74+
try
75+
{
76+
await _emailService.SendEmailAsync(
77+
preferences.EmailAddress,
78+
title,
79+
message);
80+
81+
notification.EmailSent = true;
82+
notification.EmailSentOn = DateTime.UtcNow;
83+
}
84+
catch (Exception ex)
85+
{
86+
_logger.LogError(ex, $"Failed to send email notification to {recipientUserId}");
87+
notification.EmailError = ex.Message;
88+
}
89+
}
90+
91+
// Send SMS if enabled
92+
if (notification.SendSMS && !string.IsNullOrEmpty(preferences.PhoneNumber))
93+
{
94+
try
95+
{
96+
await _smsService.SendSMSAsync(
97+
preferences.PhoneNumber,
98+
$"{title}: {message}");
99+
100+
notification.SMSSent = true;
101+
notification.SMSSentOn = DateTime.UtcNow;
102+
}
103+
catch (Exception ex)
104+
{
105+
_logger.LogError(ex, $"Failed to send SMS notification to {recipientUserId}");
106+
notification.SMSError = ex.Message;
107+
}
108+
}
109+
110+
await UpdateAsync(notification);
111+
112+
return notification;
113+
}
114+
115+
/// <summary>
116+
/// Mark notification as read
117+
/// </summary>
118+
public async Task MarkAsReadAsync(Guid notificationId)
119+
{
120+
var notification = await GetByIdAsync(notificationId);
121+
if (notification == null) return;
122+
123+
notification.IsRead = true;
124+
notification.ReadOn = DateTime.UtcNow;
125+
126+
await UpdateAsync(notification);
127+
}
128+
129+
/// <summary>
130+
/// Get unread notifications for current user
131+
/// </summary>
132+
public async Task<List<Notification>> GetUnreadNotificationsAsync()
133+
{
134+
var userId = await _userContext.GetUserIdAsync();
135+
var organizationId = await _userContext.GetActiveOrganizationIdAsync();
136+
137+
return await _context.Notifications
138+
.Where(n => n.OrganizationId == organizationId
139+
&& n.RecipientUserId == userId
140+
&& !n.IsRead
141+
&& !n.IsDeleted)
142+
.OrderByDescending(n => n.SentOn)
143+
.Take(50)
144+
.ToListAsync();
145+
}
146+
147+
/// <summary>
148+
/// Get notification history for current user
149+
/// </summary>
150+
public async Task<List<Notification>> GetNotificationHistoryAsync(int count = 100)
151+
{
152+
var userId = await _userContext.GetUserIdAsync();
153+
var organizationId = await _userContext.GetActiveOrganizationIdAsync();
154+
155+
return await _context.Notifications
156+
.Where(n => n.OrganizationId == organizationId
157+
&& n.RecipientUserId == userId
158+
&& !n.IsDeleted)
159+
.OrderByDescending(n => n.SentOn)
160+
.Take(count)
161+
.ToListAsync();
162+
}
163+
164+
/// <summary>
165+
/// Get or create notification preferences for user
166+
/// </summary>
167+
private async Task<NotificationPreferences> GetNotificationPreferencesAsync(string userId)
168+
{
169+
var organizationId = await _userContext.GetActiveOrganizationIdAsync();
170+
171+
var preferences = await _context.NotificationPreferences
172+
.FirstOrDefaultAsync(p => p.OrganizationId == organizationId
173+
&& p.UserId == userId
174+
&& !p.IsDeleted);
175+
176+
if (preferences == null)
177+
{
178+
// Create default preferences
179+
preferences = new NotificationPreferences
180+
{
181+
Id = Guid.NewGuid(),
182+
OrganizationId = organizationId!.Value,
183+
UserId = userId,
184+
EnableInAppNotifications = true,
185+
EnableEmailNotifications = true,
186+
EnableSMSNotifications = false,
187+
EmailLeaseExpiring = true,
188+
EmailPaymentDue = true,
189+
EmailPaymentReceived = true,
190+
EmailApplicationStatusChange = true,
191+
EmailMaintenanceUpdate = true,
192+
EmailInspectionScheduled = true
193+
};
194+
195+
_context.NotificationPreferences.Add(preferences);
196+
await _context.SaveChangesAsync();
197+
}
198+
199+
return preferences;
200+
}
201+
202+
private bool ShouldSendEmail(string category, NotificationPreferences prefs)
203+
{
204+
return category switch
205+
{
206+
NotificationConstants.Categories.Lease => prefs.EmailLeaseExpiring,
207+
NotificationConstants.Categories.Payment => prefs.EmailPaymentDue,
208+
NotificationConstants.Categories.Application => prefs.EmailApplicationStatusChange,
209+
NotificationConstants.Categories.Maintenance => prefs.EmailMaintenanceUpdate,
210+
NotificationConstants.Categories.Inspection => prefs.EmailInspectionScheduled,
211+
_ => true
212+
};
213+
}
214+
215+
private bool ShouldSendSMS(string category, NotificationPreferences prefs)
216+
{
217+
return category switch
218+
{
219+
NotificationConstants.Categories.Payment => prefs.SMSPaymentDue,
220+
NotificationConstants.Categories.Maintenance => prefs.SMSMaintenanceEmergency,
221+
NotificationConstants.Categories.Lease => prefs.SMSLeaseExpiringUrgent,
222+
_ => false
223+
};
224+
}
225+
}
226+
}

0 commit comments

Comments
 (0)