Skip to content

Commit 5481d17

Browse files
committed
Completely revamp notifications
1 parent 1dadbf5 commit 5481d17

38 files changed

+3808
-575
lines changed

backend/NXTBackend.API.Core/NXTBackend.API.Core.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
12-
<PackageReference Include="Snowberry.IO" Version="2.0.0.1" />
12+
<PackageReference Include="Snowberry.IO" Version="2.1.0" />
1313
</ItemGroup>
1414

1515
<ItemGroup>
1616
<ProjectReference Include="..\NXTBackend.API.Infrastructure\NXTBackend.API.Infrastructure.csproj" />
1717
<ProjectReference Include="..\NXTBackend.API.Models\NXTBackend.API.Models.csproj" />
1818
</ItemGroup>
1919

20+
<ItemGroup>
21+
<None Include="Notifications\**\*.html">
22+
<Link>templates\%(Filename)%(Extension)</Link>
23+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24+
</None>
25+
</ItemGroup>
2026
</Project>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Net.Mail;
2+
3+
namespace NXTBackend.API.Core.Notifications;
4+
5+
/// <summary>
6+
/// Represents an abstract base class for notifications that can be sent through various channels.
7+
/// </summary>
8+
public abstract class Notification()
9+
{
10+
/// <summary>
11+
/// Gets the view name for this notification.
12+
/// Can be overridden by implementing classes.
13+
/// </summary>
14+
public virtual string View => nameof(Notification);
15+
16+
/// <summary>
17+
/// Sends this notification as an email.
18+
/// </summary>
19+
public virtual MailMessage ToMail() => new();
20+
21+
/// <summary>
22+
/// Sends this notification as a text message.
23+
/// </summary>
24+
public virtual string ToText() => string.Empty;
25+
26+
/// <summary>
27+
/// Stores this notification in the database using the provided service.
28+
/// Must be implemented by derived classes.
29+
/// </summary>
30+
public abstract Domain.Entities.Notification ToDatabase();
31+
32+
/// <summary>
33+
/// Determines whether this notification should be sent.
34+
/// </summary>
35+
public virtual bool ShouldSend() => true;
36+
37+
/// <summary>
38+
/// Retrieves the View (HTML) of this notification
39+
/// </summary>
40+
/// <param name="name"></param>
41+
/// <returns></returns>
42+
public string GetTemplate()
43+
{
44+
// string cacheKey = $"template_{View}";
45+
// var cachedTemplate = _cache.GetString(cacheKey);
46+
// if (!string.IsNullOrEmpty(cachedTemplate))
47+
// return cachedTemplate;
48+
49+
// Cache miss - read from file
50+
string templatePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "templates", $"{View}.html");
51+
string template = File.ReadAllText(templatePath);
52+
// var cacheOptions = new DistributedCacheEntryOptions
53+
// {
54+
// AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24)
55+
// };
56+
57+
// _cache.SetString(cacheKey, template, cacheOptions);
58+
return template;
59+
}
60+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Notifications
2+
3+
Notifications are split into individual directories where the dir name is the type of Notification and in that dir you have:
4+
- .cs file to Setup the way to represent the / construct the notification
5+
- .cshtml file to visually represent the email.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Net.Mail;
2+
using System.Threading.Tasks;
3+
using NXTBackend.API.Core.Services;
4+
using NXTBackend.API.Core.Services.Interface;
5+
using NXTBackend.API.Domain.Entities.Users;
6+
using System.Web;
7+
using NXTBackend.API.Core.Utils;
8+
using Microsoft.AspNetCore.Http;
9+
using System.Net;
10+
11+
namespace NXTBackend.API.Core.Notifications.Welcome;
12+
13+
public class Welcome(User user) : Notification
14+
{
15+
public override string View => nameof(Welcome);
16+
17+
public override MailMessage ToMail()
18+
{
19+
var mail = new MailMessage();
20+
21+
mail.To.Add(user.Details?.Email ?? throw new ServiceException("No Email!"));
22+
mail.Subject = "Welcome to our platform!";
23+
24+
// Render the view to HTML
25+
// TODO: Instead I may want to use Razor, but I don't wanna spend 6 years implementing that now
26+
mail.Body = GetTemplate()
27+
.Replace("{{login}}", user.Login);
28+
mail.IsBodyHtml = true;
29+
30+
return mail;
31+
}
32+
33+
public override bool ShouldSend()
34+
{
35+
return user.Details?.Email is not null;
36+
}
37+
38+
public override Domain.Entities.Notification ToDatabase()
39+
{
40+
throw new NotImplementedException();
41+
}
42+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2+
<html dir="ltr" lang="en">
3+
<head>
4+
<link
5+
rel="preload"
6+
as="image"
7+
href="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-logo.png" />
8+
<link
9+
rel="preload"
10+
as="image"
11+
href="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-user.png" />
12+
<link
13+
rel="preload"
14+
as="image"
15+
href="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-arrow.png" />
16+
<link
17+
rel="preload"
18+
as="image"
19+
href="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-team.png" />
20+
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
21+
<meta name="x-apple-disable-message-reformatting" />
22+
<!--$-->
23+
</head>
24+
<body
25+
style='margin-left:auto;margin-right:auto;margin-top:auto;margin-bottom:auto;background-color:rgb(255,255,255);padding-left:0.5rem;padding-right:0.5rem;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'>
26+
<div
27+
style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0"
28+
data-skip-in-text="true">
29+
Join Alan on Vercel
30+
</div>
31+
<table
32+
align="center"
33+
width="100%"
34+
border="0"
35+
cellpadding="0"
36+
cellspacing="0"
37+
role="presentation"
38+
style="margin-left:auto;margin-right:auto;margin-top:40px;margin-bottom:40px;max-width:465px;border-radius:0.25rem;border-width:1px;border-color:rgb(234,234,234);border-style:solid;padding:20px">
39+
<tbody>
40+
<tr style="width:100%">
41+
<td>
42+
<table
43+
align="center"
44+
width="100%"
45+
border="0"
46+
cellpadding="0"
47+
cellspacing="0"
48+
role="presentation"
49+
style="margin-top:32px">
50+
<tbody>
51+
<tr>
52+
<td>
53+
<img
54+
alt="Vercel Logo"
55+
height="37"
56+
src="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-logo.png"
57+
style="margin-left:auto;margin-right:auto;margin-top:0px;margin-bottom:0px;display:block;outline:none;border:none;text-decoration:none"
58+
width="40" />
59+
</td>
60+
</tr>
61+
</tbody>
62+
</table>
63+
<h1
64+
style="margin-left:0px;margin-right:0px;margin-top:30px;margin-bottom:30px;padding:0px;text-align:center;font-weight:400;font-size:24px;color:rgb(0,0,0)">
65+
Join <strong>Enigma</strong> on <strong>Vercel</strong>
66+
</h1>
67+
<p
68+
style="font-size:14px;color:rgb(0,0,0);line-height:24px;margin-top:16px;margin-bottom:16px">
69+
Hello
70+
<!-- -->alanturing<!-- -->,
71+
</p>
72+
<p
73+
style="font-size:14px;color:rgb(0,0,0);line-height:24px;margin-top:16px;margin-bottom:16px">
74+
<strong>Alan</strong> (<a
75+
href="mailto:[email protected]"
76+
style="color:rgb(37,99,235);text-decoration-line:none"
77+
target="_blank"
78+
79+
>) has invited you to the <strong>Enigma</strong> team on<!-- -->
80+
<strong>Vercel</strong>.
81+
</p>
82+
<table
83+
align="center"
84+
width="100%"
85+
border="0"
86+
cellpadding="0"
87+
cellspacing="0"
88+
role="presentation">
89+
<tbody>
90+
<tr>
91+
<td>
92+
<table
93+
align="center"
94+
width="100%"
95+
border="0"
96+
cellpadding="0"
97+
cellspacing="0"
98+
role="presentation">
99+
<tbody style="width:100%">
100+
<tr style="width:100%">
101+
<td align="right" data-id="__react-email-column">
102+
<img
103+
alt="alanturing&#x27;s profile picture"
104+
height="64"
105+
src="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-user.png"
106+
style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none"
107+
width="64" />
108+
</td>
109+
<td align="center" data-id="__react-email-column">
110+
<img
111+
alt="Arrow indicating invitation"
112+
height="9"
113+
src="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-arrow.png"
114+
style="display:block;outline:none;border:none;text-decoration:none"
115+
width="12" />
116+
</td>
117+
<td align="left" data-id="__react-email-column">
118+
<img
119+
alt="Enigma team logo"
120+
height="64"
121+
src="https://react-email-demo-bff2pugtq-resend.vercel.app/static/vercel-team.png"
122+
style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none"
123+
width="64" />
124+
</td>
125+
</tr>
126+
</tbody>
127+
</table>
128+
</td>
129+
</tr>
130+
</tbody>
131+
</table>
132+
<table
133+
align="center"
134+
width="100%"
135+
border="0"
136+
cellpadding="0"
137+
cellspacing="0"
138+
role="presentation"
139+
style="margin-top:32px;margin-bottom:32px;text-align:center">
140+
<tbody>
141+
<tr>
142+
<td>
143+
<a
144+
href="https://vercel.com"
145+
style="border-radius:0.25rem;background-color:rgb(0,0,0);padding-left:1.25rem;padding-right:1.25rem;padding-top:0.75rem;padding-bottom:0.75rem;text-align:center;font-weight:600;font-size:12px;color:rgb(255,255,255);text-decoration-line:none;line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px 12px 20px"
146+
target="_blank"
147+
><span
148+
><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>&#8202;&#8202;</i><![endif]--></span
149+
><span
150+
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px"
151+
>Join the team</span
152+
><span
153+
><!--[if mso]><i style="mso-font-width:500%" hidden>&#8202;&#8202;&#8203;</i><![endif]--></span
154+
></a
155+
>
156+
</td>
157+
</tr>
158+
</tbody>
159+
</table>
160+
<p
161+
style="font-size:14px;color:rgb(0,0,0);line-height:24px;margin-top:16px;margin-bottom:16px">
162+
or copy and paste this URL into your browser:<!-- -->
163+
<a
164+
href="https://vercel.com"
165+
style="color:rgb(37,99,235);text-decoration-line:none"
166+
target="_blank"
167+
>https://vercel.com</a
168+
>
169+
</p>
170+
<hr
171+
style="margin-left:0px;margin-right:0px;margin-top:26px;margin-bottom:26px;width:100%;border-width:1px;border-color:rgb(234,234,234);border-style:solid;border:none;border-top:1px solid #eaeaea" />
172+
<p
173+
style="color:rgb(102,102,102);font-size:12px;line-height:24px;margin-top:16px;margin-bottom:16px">
174+
This invitation was intended for<!-- -->
175+
<span style="color:rgb(0,0,0)">alanturing</span>. This invite was
176+
sent from <span style="color:rgb(0,0,0)">204.13.186.218</span>
177+
<!-- -->located in<!-- -->
178+
<span style="color:rgb(0,0,0)">São Paulo, Brazil</span>. If you
179+
were not expecting this invitation, you can ignore this email. If
180+
you are concerned about your account&#x27;s safety, please reply
181+
to this email to get in touch with us.
182+
</p>
183+
</td>
184+
</tr>
185+
</tbody>
186+
</table>
187+
<!--/$-->
188+
</body>
189+
</html>

0 commit comments

Comments
 (0)