Skip to content

Commit

Permalink
feat: implement email sending with sengrid #20
Browse files Browse the repository at this point in the history
feat: customize email sending of identity #20
  • Loading branch information
GenjiruSUchiwa committed Dec 6, 2024
1 parent 2984aad commit 554cd82
Show file tree
Hide file tree
Showing 26 changed files with 706 additions and 60 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<PackageVersion Include="runtime.unix.System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="runtime.win7.System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="Scalar.AspNetCore" Version="1.2.43" />
<PackageVersion Include="SendGrid" Version="9.29.3" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" />
Expand Down
22 changes: 21 additions & 1 deletion Place.sln
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{CC68CA38-62B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Place.API", "src\Place.API\Place.API.csproj", "{C4DD7B27-138C-4DFC-BC57-69BFC70853CA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{3F0BAB74-75A0-4901-85CC-2F4D3BA668AD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notification", "Notification", "{9AE3E340-7217-415E-B4E2-34F22EC50D53}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Place.Notification", "src\Place.Notification\Place.Notification.csproj", "{4CAF9B33-94CC-40E7-B6F3-86BF4538FB9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Identity.UnitTests", "tests\Core.Identity.UnitTests\Core.Identity.UnitTests.csproj", "{512A629D-3BAA-4AAF-ACF2-47B6E6653C15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -69,7 +77,6 @@ Global
{69B31787-CBE3-4009-B7AA-E261200F93E7} = {5B48E2F5-D7CC-48EA-94FE-A2F132650C8F}
{B6BB1030-4DB5-497F-AAC9-E2F5D2252AD3} = {5B48E2F5-D7CC-48EA-94FE-A2F132650C8F}
{69A29059-CFF9-4B4F-BB68-55DEE27F3910} = {25654B5F-D4E9-4019-B307-8759C38E94A8}
{31B51268-2826-4CB5-B1FC-80519BB3ED07} = {25654B5F-D4E9-4019-B307-8759C38E94A8}
{5D217518-06F0-4D31-8969-2EAFF2A7D1F6} = {25654B5F-D4E9-4019-B307-8759C38E94A8}
{2AF91DCD-AABF-4EF0-844F-2319A1FAA4B5} = {25654B5F-D4E9-4019-B307-8759C38E94A8}
{C37B0761-0FF0-46F4-932B-B2C42C926E73} = {25654B5F-D4E9-4019-B307-8759C38E94A8}
Expand All @@ -82,6 +89,11 @@ Global
{5B48E2F5-D7CC-48EA-94FE-A2F132650C8F} = {4B354D87-FCA4-46F3-A64E-76376D3C68C2}
{C3F80B30-C2DE-41DC-933A-A24E04827504} = {4B354D87-FCA4-46F3-A64E-76376D3C68C2}
{C4DD7B27-138C-4DFC-BC57-69BFC70853CA} = {CC68CA38-62BE-4535-9BB4-AB1597AFC90A}
{3F0BAB74-75A0-4901-85CC-2F4D3BA668AD} = {25654B5F-D4E9-4019-B307-8759C38E94A8}
{31B51268-2826-4CB5-B1FC-80519BB3ED07} = {3F0BAB74-75A0-4901-85CC-2F4D3BA668AD}
{9AE3E340-7217-415E-B4E2-34F22EC50D53} = {4B354D87-FCA4-46F3-A64E-76376D3C68C2}
{4CAF9B33-94CC-40E7-B6F3-86BF4538FB9B} = {9AE3E340-7217-415E-B4E2-34F22EC50D53}
{512A629D-3BAA-4AAF-ACF2-47B6E6653C15} = {3F0BAB74-75A0-4901-85CC-2F4D3BA668AD}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1DFC8537-6B29-4BB5-8449-1910496DB479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -146,5 +158,13 @@ Global
{C4DD7B27-138C-4DFC-BC57-69BFC70853CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4DD7B27-138C-4DFC-BC57-69BFC70853CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4DD7B27-138C-4DFC-BC57-69BFC70853CA}.Release|Any CPU.Build.0 = Release|Any CPU
{4CAF9B33-94CC-40E7-B6F3-86BF4538FB9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CAF9B33-94CC-40E7-B6F3-86BF4538FB9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CAF9B33-94CC-40E7-B6F3-86BF4538FB9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CAF9B33-94CC-40E7-B6F3-86BF4538FB9B}.Release|Any CPU.Build.0 = Release|Any CPU
{512A629D-3BAA-4AAF-ACF2-47B6E6653C15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{512A629D-3BAA-4AAF-ACF2-47B6E6653C15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{512A629D-3BAA-4AAF-ACF2-47B6E6653C15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{512A629D-3BAA-4AAF-ACF2-47B6E6653C15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
2 changes: 2 additions & 0 deletions src/Common/Core.Identity/Core.Identity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Update="System.Private.Uri" />
<PackageReference Include="SendGrid" />
<PackageReference Include="System.Private.Uri" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Place.Notification\Place.Notification.csproj" />
<ProjectReference Include="..\Core.EF\Core.EF.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
Expand Down
22 changes: 0 additions & 22 deletions src/Common/Core.Identity/EmailSender.cs

This file was deleted.

110 changes: 110 additions & 0 deletions src/Common/Core.Identity/IdentityEmailSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Place.Notification;
using Place.Notification.Email;
using Place.Notification.Email.SMTP;

namespace Core.Identity;

/// <inheritdoc/>
public class IdentityEmailSender<TUser> : IEmailSender<TUser>
where TUser : class
{
private readonly ILogger<IdentityEmailSender<TUser>> _logger;
private readonly IEmailService _emailService;

/// <exception cref="ArgumentNullException"></exception>
public IdentityEmailSender(
ILogger<IdentityEmailSender<TUser>> logger,
IEmailService emailService
)
{
_logger = logger;
_emailService = emailService;
}

/// <inheritdoc/>
public async Task SendConfirmationLinkAsync(TUser user, string email, string confirmationLink)
{
string subject = "Confirmez votre adresse email";
string htmlContent = GetConfirmationEmailTemplate(confirmationLink);

await SendEmailAsync(email, subject, htmlContent);
}

public async Task SendPasswordResetLinkAsync(TUser user, string email, string resetLink)
{
string subject = "Réinitialisation de votre mot de passe";
string htmlContent = GetPasswordResetTemplate(resetLink);

await SendEmailAsync(email, subject, htmlContent);
}

public async Task SendPasswordResetCodeAsync(TUser user, string email, string resetCode)
{
string subject = "Votre code de réinitialisation de mot de passe";
string htmlContent = GetPasswordResetTemplate(resetCode);

await SendEmailAsync(email, subject, htmlContent);
}

protected virtual async Task SendEmailAsync(string to, string subject, string htmlContent)
{
EmailMessage email = new(to, subject, htmlContent);

try
{
await _emailService.SendAsync(email);

_logger.LogInformation("Email sent successfully to {Email}", email.To);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending email to {Email}", email.To);
throw;
}
}

private string GetConfirmationEmailTemplate(string confirmationLink)
{
return $@"
<html>
<body>
<h2>Confirmez votre adresse email</h2>
<p>Pour confirmer votre compte, veuillez cliquer sur le lien ci-dessous :</p>
<p><a href='{confirmationLink}'>Confirmer mon compte</a></p>
<p>Si le lien ne fonctionne pas, copiez et collez cette URL dans votre navigateur :</p>
<p>{confirmationLink}</p>
</body>
</html>";
}

private string GetPasswordResetTemplate(string resetLink)
{
return $@"
<html>
<body>
<h2>Réinitialisation de votre mot de passe</h2>
<p>Pour réinitialiser votre mot de passe, cliquez sur le lien ci-dessous :</p>
<p><a href='{resetLink}'>Réinitialiser mon mot de passe</a></p>
<p>Si le lien ne fonctionne pas, copiez et collez cette URL dans votre navigateur :</p>
<p>{resetLink}</p>
</body>
</html>";
}

private string GetPasswordResetCodeTemplate(string resetCode)
{
return $@"
<html>
<body>
<h2>Code de réinitialisation de mot de passe</h2>
<p>Voici votre code de réinitialisation de mot de passe :</p>
<h3>{resetCode}</h3>
<p>Ce code est valable pendant une durée limitée.</p>
</body>
</html>";
}
}
6 changes: 4 additions & 2 deletions src/Common/Core.Identity/ServiceCollectionExtions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -81,7 +80,10 @@ IConfiguration configuration

public static IServiceCollection AddEmailSender(this IServiceCollection services)
{
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<
IEmailSender<ApplicationUser>,
IdentityEmailSender<ApplicationUser>
>();
return services;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Place.API/Place.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
<PackageReference Include="runtime.unix.System.Private.Uri" />
<PackageReference Include="runtime.win7.System.Private.Uri" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="SendGrid" />
<PackageReference Include="System.Private.Uri" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Account\Account.csproj" />
<ProjectReference Include="..\Common\Core.Framework\Core.Framework.csproj" />
<ProjectReference Include="..\Identity\Identity.csproj" />
<ProjectReference Include="..\Place.Notification\Place.Notification.csproj" />
</ItemGroup>

</Project>
7 changes: 6 additions & 1 deletion src/Place.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Account;
using Core.Framework;
using Core.Identity;
using Core.MediatR;
using Identity;
using Microsoft.AspNetCore.Builder;
Expand All @@ -9,7 +8,11 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Place.API;
using Place.Notification;
using Place.Notification.Email;
using Scalar.AspNetCore;
using SendGrid;
using SendGrid.Helpers.Mail;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IConfiguration configuration = builder.Configuration;
Expand All @@ -21,6 +24,8 @@

builder.Services.AddAccountModule(configuration);

builder.Services.AddEmailService(configuration);

builder.Services.AddCoreMediatR(typeof(IIdentityRoot).Assembly);

builder.Services.AddCoreMediatR(typeof(IAccountRoot).Assembly);
Expand Down
75 changes: 75 additions & 0 deletions src/Place.API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,80 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"app": {
"name": "Place Profile Api"
},
"Serilog": {
"applicationName": "identity-service",
"excludePaths": ["/ping", "/metrics"],
"level": "information",
"console": {
"enabled": true
},
"file": {
"enabled": true,
"path": "logs/logs.txt",
"interval": "day"
},
"seq": {
"enabled": true,
"url": "http://localhost:5341",
"token": "secret"
}
},
"Swagger": {
"Title": "Place Profile Api",
"Description": "Place Profile Api documentation",
"Version": "v1",
"EnableBearerAuth": true,
"SecuritySchemaName": "Bearer",
"SecurityScheme": "JWT",
"SecurityDescription": "Utiliser le format: Bearer {votre_token}",
"EnableVersioning": true,
"RoutePrefix": "swagger"
},
"ApiVersioning": {
"DefaultApiVersionMajor": 1,
"DefaultApiVersionMinor": 0,
"AssumeDefaultVersionWhenUnspecified": true,
"ReportApiVersions": true,
"ApiVersionReaderType": "Combine",
"ReaderOptions": {
"HeaderName": "x-api-version",
"QueryStringParam": "api-version",
"MediaTypeParam": "v"
},
"GroupNameFormat": "'v'VVV",
"SubstituteApiVersionInUrl": true,
"DeprecatedVersionOptions": {
"DeprecationMessage": "Cette version de l'API est obsolète. Veuillez migrer vers la version la plus récente.",
"SunsetDate": "2025-12-31",
"DocumentationUrl": "https://api.monsite.com/deprecation-policy"
},
"ApiExplorerOptions": {
"GroupNameFormat": "'v'VVV",
"SubstituteApiVersionInUrl": true,
"UrlFormat": "v{version:apiVersion}",
"AddApiVersionParametersWhenVersionNeutral": true
}
},
"ConnectionStrings": {
"PlaceDb": "Server=localhost;Port=5499;Database=place_db;User Id=postgres;Password=postgres;Include Error Detail=true"
},
"Email": {
"Provider": "SendGrid",
"From": "[email protected]",
"FromName": "Support Place",
"Smtp": {
"Host": "smtp.sendgrid.net",
"Port": 587,
"Username": "[email protected]",
"Password": "Pass0rd123!"
},
"SendGrid": {
"ApiKey": null
}
}

}
Loading

0 comments on commit 554cd82

Please sign in to comment.