Skip to content

Commit

Permalink
Add email feature
Browse files Browse the repository at this point in the history
  • Loading branch information
mythz committed May 10, 2024
1 parent 4dd9527 commit 8593d1f
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 18 deletions.
29 changes: 21 additions & 8 deletions MyApp.ServiceInterface/CreatorKit/EmailRenderersServices.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using ServiceStack;
using ServiceStack;
using ServiceStack.Script;
using CreatorKit.ServiceModel;
using Markdig;
using Markdig.Syntax;
using MyApp.ServiceModel;
using ServiceStack.OrmLite;

namespace CreatorKit.ServiceInterface;

Expand Down Expand Up @@ -71,4 +65,23 @@ public async Task<object> Any(RenderCustomHtml request)
["body"] = evalBody,
});
}

public async Task<object> Any(RenderTagQuestionsEmail request)
{
OrmLiteUtils.PrintSql();
var context = renderer.CreateMailContext(layout:"tags", page:"tagged-questions");

var posts = await Db.SelectAsync(Db.From<Post>()
.Where(x => x.CreationDate >= request.Date && x.CreationDate < request.Date.AddDays(1))
.Where("replace(replace(tags,'[',','),']',',') LIKE '%,' || {0} || ',%'", request.Tag)
.Limit(10));

using var db = HostContext.AppHost.GetDbConnection(Databases.CreatorKit);
return await renderer.RenderToHtmlResultAsync(db, context, request,
args:new() {
["tag"] = request.Tag,
["date"] = request.Date.ToString("MMMM dd"),
[nameof(posts)] = posts,
});
}
}
32 changes: 26 additions & 6 deletions MyApp.ServiceInterface/CreatorKit/EmailServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ public async Task<object> Any(UpdateMailMessageDraft request)
if (response is IHttpError httpError)
return httpError;
var responseBody = response is IHttpResult httpResult ? httpResult.Response.ToString() : response.ToString();

if (message.Renderer == nameof(RenderSimpleText))
message.Message.BodyText = responseBody;
else
message.Message.BodyHtml = responseBody;

await db.UpdateAsync(message);

if (request.Send == true)
{
renderer.SendMailMessage(message.Id);
Expand All @@ -51,8 +51,8 @@ public async Task<object> Any(SimpleTextEmail request)
using var db = HostContext.AppHost.GetDbConnection(Databases.CreatorKit);
var contact = await db.GetOrCreateContact(request);
var viewRequest = request.ConvertTo<RenderSimpleText>().FromContact(contact);
var bodyText = (string) await Gateway.SendAsync(typeof(string), viewRequest);
var bodyText = (string)await Gateway.SendAsync(typeof(string), viewRequest);

var email = await renderer.CreateMessageAsync(db, new MailMessage
{
Draft = request.Draft ?? false,
Expand All @@ -72,7 +72,7 @@ public async Task<object> Any(CustomHtmlEmail request)
using var db = HostContext.AppHost.GetDbConnection(Databases.CreatorKit);
var contact = await db.GetOrCreateContact(request);
var viewRequest = request.ConvertTo<RenderCustomHtml>().FromContact(contact);
var bodyHtml = (string) await Gateway.SendAsync(typeof(string), viewRequest);
var bodyHtml = (string)await Gateway.SendAsync(typeof(string), viewRequest);

var email = await renderer.CreateMessageAsync(db, new MailMessage
{
Expand All @@ -86,5 +86,25 @@ public async Task<object> Any(CustomHtmlEmail request)
},
}.FromRequest(viewRequest));
return email;
}
}

public async Task<object> Any(TagQuestionsEmail request)
{
using var db = HostContext.AppHost.GetDbConnection(Databases.CreatorKit);
var contact = await db.GetOrCreateContact(request);
var viewRequest = request.ConvertTo<RenderTagQuestionsEmail>().FromContact(contact);
var bodyHtml = (string)await Gateway.SendAsync(typeof(string), viewRequest);

var email = await renderer.CreateMessageAsync(db, new MailMessage
{
Draft = request.Draft ?? false,
Message = new EmailMessage
{
To = contact.ToMailTos(),
Subject = $"New {request.Tag} questions for {request.Date:MMMM dd} - pvq.app",
BodyHtml = bodyHtml,
},
}.FromRequest(viewRequest));
return email;
}
}
13 changes: 12 additions & 1 deletion MyApp.ServiceModel/CreatorKit/EmailRenderers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,15 @@ public class RenderDoc : IGet, IReturn<string>
{
[ValidateNotEmpty]
public string Page { get; set; }
}
}

[Tag(ServiceModel.Tag.Mail), ValidateIsAdmin, ExcludeMetadata]
public class RenderTagQuestionsEmail : RenderEmailBase, IGet, IReturn<string>
{
[ValidateNotEmpty]
public string Tag { get; set; }

[ValidateNotEmpty]
public DateTime Date { get; set; }
}

19 changes: 17 additions & 2 deletions MyApp.ServiceModel/CreatorKit/Emails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace CreatorKit.ServiceModel;

[Renderer(typeof(RenderSimpleText))]
[Renderer<RenderSimpleText>]
[Tag(Tag.Mail), ValidateIsAdmin]
[Description("Simple Text Email")]
public class SimpleTextEmail : CreateEmailBase, IPost, IReturn<MailMessage>
Expand All @@ -19,7 +19,7 @@ public class SimpleTextEmail : CreateEmailBase, IPost, IReturn<MailMessage>
public bool? Draft { get; set; }
}

[Renderer(typeof(RenderCustomHtml))]
[Renderer<RenderCustomHtml>]
[Tag(Tag.Mail), ValidateIsAdmin]
[Icon(Svg = Icons.RichHtml)]
[Description("Custom HTML Email")]
Expand All @@ -41,3 +41,18 @@ public class CustomHtmlEmail : CreateEmailBase, IPost, IReturn<MailMessage>
public string? Body { get; set; }
public bool? Draft { get; set; }
}

[Renderer<RenderTagQuestionsEmail>]
[Tag(ServiceModel.Tag.Mail), ValidateIsAdmin]
[Description("New Questions with Tag")]
public class TagQuestionsEmail : CreateEmailBase, IPost, IReturn<MailMessage>
{
[ValidateNotEmpty]
public string Tag { get; set; }

[ValidateNotEmpty]
public DateTime Date { get; set; }

public bool? Draft { get; set; }
}

2 changes: 1 addition & 1 deletion MyApp.ServiceModel/CreatorKit/RendererAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace CreatorKit.ServiceModel;

[AttributeUsage(AttributeTargets.Class)]
public class RendererAttribute<T>(string template) : RendererAttribute(typeof(T)) {}
public class RendererAttribute<T>() : RendererAttribute(typeof(T)) {}

/// <summary>
/// Specify which renderer should be used to render emails
Expand Down
37 changes: 37 additions & 0 deletions MyApp/Components/Pages/CommunityRules.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@page "/community-rules"

<PageTitle>Community Rules</PageTitle>

pvq.app is where anyone is welcome to learn about ServiceStack and .NET development.
We want to keep it a welcome place, so we have created this ruleset to help guide the content posted on pvq.app.

If you see a post or comment that breaks the rules, we welcome you to report it to the our moderators.

These rules apply to all community aspects on pvq.app: all parts of a public post (title, description, tags, visual content), comments, links, and messages.
Moderators consider context and intent while enforcing the community rules.

- No nudity or sexually explicit content.
- Provocative, inflammatory, unsettling, or suggestive content should be marked as Mature.
- No hate speech, abuse, or harassment.
- No content that condones illegal or violent activity.
- No gore or shock content.
- No posting personal information.

### Good Sharing Practices

Considering these tips when sharing with the pvq.app community will help ensure you're contributing great content.

#### 1. Value
- Good sharing means posting content which brings value to the community. Content which opens up a discussion, shares something new and unique, or has a deeper story to tell beyond the image itself is content that generally brings value. Ask yourself first: is this something I would be interested in seeing if someone else posted it?
#### 2. Transparency
- We expect that the original poster (OP) will be explicit about if and how they are connected to the content they are posting. Trying to hide that relationship, or not explaining it well to others, is a common feature of bad sharing.
#### 3. Respect
- Good sharing means knowing when the community has spoken through upvotes and downvotes and respecting that. You should avoid constantly reposting content to User Submitted that gets downvoted. This kind of spamming annoys the community, and it won't make your posts any more popular.
Repeated violations of the good sharing practices after warning may result in account ban.


If content breaks these community rules, it will be removed and the original poster warned about the removal.
Warnings will expire. If multiple submissions break the rules in a short time frame, warnings will accumulate, which could lead to a 24-hour suspension, and further, a ban.

If you aren't sure if your post fits the community rules, please don't post it.
Just because you've seen a rule-breaking image posted somewhere else on pvq.app doesn't mean it's okay for you to repost it.
18 changes: 18 additions & 0 deletions MyApp/Components/Pages/MailPreferences.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@page "/mail-preferences"
<PageTitle>Manage your mail preferences</PageTitle>

<div class="flex justify-center">
<svg class="w-20 h-20" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M5 5h13a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3m0 1c-.5 0-.94.17-1.28.47l7.78 5.03l7.78-5.03C18.94 6.17 18.5 6 18 6H5m6.5 6.71L3.13 7.28C3.05 7.5 3 7.75 3 8v9a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2V8c0-.25-.05-.5-.13-.72l-8.37 5.43Z"/></svg>
</div>

Manage your communications with ServiceStack by selecting updates you'd like to receive.

<div class="not-prose flex justify-center">
<div data-mail="MailPreferences"></div>
</div>

<script type="module">
import { mail } from '/creatorkit/components/mail.mjs'
mail('[data-mail]', { mailingLists:['MonthlyNewsletter'] })
</script>
Loading

0 comments on commit 8593d1f

Please sign in to comment.