mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-30 11:38:32 -07:00
Added the ability to unsubscribe from the newsletter #2137
This commit is contained in:
parent
043eb08aa5
commit
ac3f1941bc
11 changed files with 162 additions and 22 deletions
|
@ -2,6 +2,6 @@
|
|||
{
|
||||
public interface INewsletterTemplate
|
||||
{
|
||||
string LoadTemplate(string subject, string intro, string tableHtml, string logo);
|
||||
string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
|
|||
if (string.IsNullOrEmpty(_templateLocation))
|
||||
{
|
||||
#if DEBUG
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp3.0", "Templates", "NewsletterTemplate.html");
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net5.0", "Templates", "NewsletterTemplate.html");
|
||||
#else
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html");
|
||||
#endif
|
||||
|
@ -29,9 +29,10 @@ namespace Ombi.Notifications.Templates
|
|||
private const string Logo = "{@LOGO}";
|
||||
private const string TableLocation = "{@RECENTLYADDED}";
|
||||
private const string IntroText = "{@INTRO}";
|
||||
private const string Unsubscribe = "{@UNSUBSCRIBE}";
|
||||
|
||||
|
||||
public string LoadTemplate(string subject, string intro, string tableHtml, string logo)
|
||||
public string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink)
|
||||
{
|
||||
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
|
||||
sb.Replace(SubjectKey, subject);
|
||||
|
@ -39,6 +40,7 @@ namespace Ombi.Notifications.Templates
|
|||
sb.Replace(IntroText, intro);
|
||||
sb.Replace(DateKey, DateTime.Now.ToString("f"));
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
|
||||
sb.Replace(Unsubscribe, string.IsNullOrEmpty(unsubscribeLink) ? string.Empty : unsubscribeLink);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
|
|
@ -451,6 +451,11 @@
|
|||
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="content-block powered-by" valign="top" align="center" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;">
|
||||
<a href="{@UNSUBSCRIBE}" style="font-weight: 400; font-size: 12px; text-align: center; color: #ff761b;">Unsubscribe</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-block powered-by" valign="top" align="center" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;">
|
||||
Powered by <a href="https://github.com/Ombi-app/Ombi" style="font-weight: 400; font-size: 12px; text-align: center; text-decoration: none; color: #ff761b;">Ombi</a>
|
||||
|
|
35
src/Ombi.Schedule.Tests/NewsletterUnsubscribeTests.cs
Normal file
35
src/Ombi.Schedule.Tests/NewsletterUnsubscribeTests.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using NUnit.Framework;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Schedule.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class NewsletterUnsubscribeTests
|
||||
{
|
||||
[TestCaseSource(nameof(Data))]
|
||||
public string GenerateUnsubscribeLinkTest(string appUrl, string id)
|
||||
{
|
||||
return NewsletterJob.GenerateUnsubscribeLink(appUrl, id);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("https://google.com/", "1").Returns("https://google.com:443/unsubscribe/1").SetName("Fully Qualified");
|
||||
yield return new TestCaseData("https://google.com", "1").Returns("https://google.com:443/unsubscribe/1").SetName("Missing Slash");
|
||||
yield return new TestCaseData("google.com", "1").Returns("http://google.com:80/unsubscribe/1").SetName("Missing scheme");
|
||||
yield return new TestCaseData("ombi.google.com", "1").Returns("http://ombi.google.com:80/unsubscribe/1").SetName("Sub domain missing scheme");
|
||||
yield return new TestCaseData("https://ombi.google.com", "1").Returns("https://ombi.google.com:443/unsubscribe/1").SetName("Sub domain");
|
||||
yield return new TestCaseData("https://ombi.google.com/", "1").Returns("https://ombi.google.com:443/unsubscribe/1").SetName("Sub domain with slash");
|
||||
yield return new TestCaseData("https://google.com/ombi/", "1").Returns("https://google.com:443/ombi/unsubscribe/1").SetName("RP");
|
||||
yield return new TestCaseData("https://google.com/ombi", "1").Returns("https://google.com:443/ombi/unsubscribe/1").SetName("RP missing slash");
|
||||
yield return new TestCaseData("https://google.com:3577", "1").Returns("https://google.com:3577/unsubscribe/1").SetName("Port");
|
||||
yield return new TestCaseData("https://google.com:3577/", "1").Returns("https://google.com:3577/unsubscribe/1").SetName("Port With Slash");
|
||||
yield return new TestCaseData("", "1").Returns(string.Empty).SetName("Missing App URL empty");
|
||||
yield return new TestCaseData(null, "1").Returns(string.Empty).SetName("Missing App URL null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -228,32 +228,33 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
var messageContent = ParseTemplate(template, customization);
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
|
||||
var bodyBuilder = new BodyBuilder
|
||||
{
|
||||
HtmlBody = html,
|
||||
};
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = bodyBuilder.ToMessageBody(),
|
||||
Subject = messageContent.Subject
|
||||
};
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
var url = GenerateUnsubscribeLink(customization.ApplicationUrl, user.Id);
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo, url);
|
||||
|
||||
var bodyBuilder = new BodyBuilder
|
||||
{
|
||||
HtmlBody = html,
|
||||
};
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = bodyBuilder.ToMessageBody(),
|
||||
Subject = messageContent.Subject
|
||||
};
|
||||
|
||||
// Get the users to send it to
|
||||
if (user.Email.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// BCC the messages
|
||||
message.Bcc.Add(new MailboxAddress(user.Email.Trim(), user.Email.Trim()));
|
||||
}
|
||||
// Send the message to the user
|
||||
message.To.Add(new MailboxAddress(user.Email.Trim(), user.Email.Trim()));
|
||||
|
||||
// Send the email
|
||||
await _email.Send(message, emailSettings);
|
||||
// Send the email
|
||||
await _email.Send(message, emailSettings);
|
||||
}
|
||||
|
||||
// Now add all of this to the Recently Added log
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
|
@ -346,11 +347,14 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var unsubscribeLink = GenerateUnsubscribeLink(customization.ApplicationUrl, a.Id);
|
||||
|
||||
var messageContent = ParseTemplate(template, customization);
|
||||
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo, unsubscribeLink);
|
||||
|
||||
await _email.Send(
|
||||
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
|
||||
|
@ -371,6 +375,21 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
.SendAsync(NotificationHub.NotificationEvent, "Newsletter Finished");
|
||||
}
|
||||
|
||||
public static string GenerateUnsubscribeLink(string applicationUrl, string id)
|
||||
{
|
||||
if (!applicationUrl.HasValue())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (!applicationUrl.EndsWith('/'))
|
||||
{
|
||||
applicationUrl += '/';
|
||||
}
|
||||
var b = new UriBuilder($"{applicationUrl}unsubscribe/{id}");
|
||||
return b.ToString();
|
||||
}
|
||||
|
||||
private async Task<HashSet<PlexServerContent>> GetMoviesWithoutId(HashSet<int> addedMovieLogIds, HashSet<PlexServerContent> needsMovieDbPlex)
|
||||
{
|
||||
foreach (var movie in needsMovieDbPlex)
|
||||
|
|
|
@ -91,6 +91,7 @@ const routes: Routes = [
|
|||
{ loadChildren: () => import("./vote/vote.module").then(m => m.VoteModule), path: "vote" },
|
||||
{ loadChildren: () => import("./media-details/media-details.module").then(m => m.MediaDetailsModule), path: "details" },
|
||||
{ loadChildren: () => import("./user-preferences/user-preferences.module").then(m => m.UserPreferencesModule), path: "user-preferences" },
|
||||
{ loadChildren: () => import("./unsubscribe/unsubscribe.module").then(m => m.UnsubscribeModule), path: "unsubscribe" },
|
||||
];
|
||||
|
||||
|
||||
|
|
|
@ -95,4 +95,8 @@ export class IdentityService extends ServiceHelpers {
|
|||
public updateStreamingCountry(code: string): Observable<null> {
|
||||
return this.http.post<any>(`${this.url}streamingcountry`, {code: code}, {headers: this.headers});
|
||||
}
|
||||
|
||||
public unsubscribeNewsletter(userId: string): Observable<any>{
|
||||
return this.http.get<any>(`${this.url}newsletter/unsubscribe/${userId}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="wizard-background">
|
||||
<h2 style="color:white">Unsubscribed!</h2>
|
||||
</div>
|
|
@ -0,0 +1,28 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { IdentityService, SettingsService } from "../../../services";
|
||||
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./unsubscribe-confirm.component.html",
|
||||
})
|
||||
export class UnsubscribeConfirmComponent implements OnInit {
|
||||
|
||||
private userId: string;
|
||||
|
||||
constructor(private authService: AuthService,
|
||||
private readonly identityService: IdentityService,
|
||||
private readonly settingsService: SettingsService,
|
||||
private route: ActivatedRoute) {
|
||||
this.route.params.subscribe(async (params: any) => {
|
||||
if (typeof params.id === 'string' || params.id instanceof String) {
|
||||
this.userId = params.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async ngOnInit() {
|
||||
this.identityService.unsubscribeNewsletter(this.userId).subscribe()
|
||||
}
|
||||
}
|
27
src/Ombi/ClientApp/src/app/unsubscribe/unsubscribe.module.ts
Normal file
27
src/Ombi/ClientApp/src/app/unsubscribe/unsubscribe.module.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { UnsubscribeConfirmComponent } from "./components/confirm-component/unsubscribe-confirm.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: ":id", component: UnsubscribeConfirmComponent},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes),
|
||||
],
|
||||
declarations: [
|
||||
UnsubscribeConfirmComponent,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
|
||||
})
|
||||
export class UnsubscribeModule { }
|
|
@ -1032,6 +1032,22 @@ namespace Ombi.Controllers.V1
|
|||
return Json(true);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("newsletter/unsubscribe/{userId}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> UnsubscribeUser(string userId)
|
||||
{
|
||||
// lookup user
|
||||
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
await UserManager.RemoveFromRoleAsync(user, OmbiRoles.ReceivesNewsletter);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private async Task<List<IdentityResult>> AddRoles(IEnumerable<ClaimCheckboxes> roles, OmbiUser ombiUser)
|
||||
{
|
||||
var roleResult = new List<IdentityResult>();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue