repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
6710e0c
parent
50f4110
author
Eric Bower
date
2025-03-14 10:55:24 -0400 EDT
chore(auth): better pico+ rss feed items
4 files changed,  +85, -23
M pkg/apps/auth/__snapshots__/api_test.snap
+9, -9
 1@@ -101,12 +101,12 @@ successfully added pico+ user
 2     <name>team pico</name>
 3   </author>
 4   <entry>
 5-    <title>pico+ membership activated on 2021-08-15 14:30:45</title>
 6+    <title>pico+ membership activated on 2021-08-15T14:30:45Z</title>
 7     <updated>2021-08-15T14:30:45Z</updated>
 8     <id>pico-plus-activated-1629037845</id>
 9-    <content type="html">Thanks for joining pico+! You now have access to all our premium services for exactly one year.  We will send you pico+ expiration notifications through this RSS feed.  Go to &lt;a href=&#34;https://pico.sh/getting-started#next-steps&#34;&gt;pico.sh/getting-started#next-steps&lt;/a&gt; to start using our services.</content>
10+    <content type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ activated&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;Thanks for joining &lt;code&gt;pico+&lt;/code&gt;!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;You now have access to all our premium services until &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;We will send you &lt;code&gt;pico+&lt;/code&gt; expiration notifications through this RSS feed.&#xA;&#x9;Go to &lt;a href=&#34;https://pico.sh/getting-started#next-steps&#34;&gt;pico.sh/getting-started#next-steps&lt;/a&gt;&#xA;&#x9;to start using our services.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</content>
11     <link href="https://pico.sh" rel="alternate"></link>
12-    <summary type="html">Thanks for joining pico+! You now have access to all our premium services for exactly one year.  We will send you pico+ expiration notifications through this RSS feed.  Go to &lt;a href=&#34;https://pico.sh/getting-started#next-steps&#34;&gt;pico.sh/getting-started#next-steps&lt;/a&gt; to start using our services.</summary>
13+    <summary type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ activated&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;Thanks for joining &lt;code&gt;pico+&lt;/code&gt;!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;You now have access to all our premium services until &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;We will send you &lt;code&gt;pico+&lt;/code&gt; expiration notifications through this RSS feed.&#xA;&#x9;Go to &lt;a href=&#34;https://pico.sh/getting-started#next-steps&#34;&gt;pico.sh/getting-started#next-steps&lt;/a&gt;&#xA;&#x9;to start using our services.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</summary>
14     <author>
15       <name>team pico</name>
16     </author>
17@@ -115,9 +115,9 @@ successfully added pico+ user
18     <title>pico+ 1-month expiration notice</title>
19     <updated>2021-07-16T14:30:45Z</updated>
20     <id>1626445845</id>
21-    <content type="html">Your pico+ membership is going to expire on 2021-08-16 14:30:45</content>
22+    <content type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ 1-month expiration notification!&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;pico+ 1-month expiration notification!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;Your &lt;code&gt;pico+&lt;/code&gt; membership will expire on &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If your pico+ membership expires then we will:&#xA;&#xA;&#x9;&lt;ul&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to &lt;a href=&#34;https&#34;&gt;https://tuns.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;reject new sites being created for &lt;a href=&#34;https://pgs.sh&#34;&gt;pgs.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to our IRC bouncer&lt;/li&gt;&#xA;&#x9;&lt;/ul&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;In order to continue using our premium services, you need to purchase another year:&#xA;&#x9;&lt;a href=&#34;https://auth.pico.sh/checkout/user-a&#34;&gt;purchase pico+&lt;/a&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</content>
23     <link href="https://pico.sh" rel="alternate"></link>
24-    <summary type="html">Your pico+ membership is going to expire on 2021-08-16 14:30:45</summary>
25+    <summary type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ 1-month expiration notification!&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;pico+ 1-month expiration notification!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;Your &lt;code&gt;pico+&lt;/code&gt; membership will expire on &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If your pico+ membership expires then we will:&#xA;&#xA;&#x9;&lt;ul&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to &lt;a href=&#34;https&#34;&gt;https://tuns.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;reject new sites being created for &lt;a href=&#34;https://pgs.sh&#34;&gt;pgs.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to our IRC bouncer&lt;/li&gt;&#xA;&#x9;&lt;/ul&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;In order to continue using our premium services, you need to purchase another year:&#xA;&#x9;&lt;a href=&#34;https://auth.pico.sh/checkout/user-a&#34;&gt;purchase pico+&lt;/a&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</summary>
26     <author>
27       <name>team pico</name>
28     </author>
29@@ -126,9 +126,9 @@ successfully added pico+ user
30     <title>pico+ 1-week expiration notice</title>
31     <updated>2021-08-09T14:30:45Z</updated>
32     <id>1628519445</id>
33-    <content type="html">Your pico+ membership is going to expire on 2021-08-16 14:30:45</content>
34+    <content type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ 1-week expiration notification!&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;pico+ 1-week expiration notification!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;Your &lt;code&gt;pico+&lt;/code&gt; membership will expire on &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If your pico+ membership expires then we will:&#xA;&#xA;&#x9;&lt;ul&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to &lt;a href=&#34;https&#34;&gt;https://tuns.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;reject new sites being created for &lt;a href=&#34;https://pgs.sh&#34;&gt;pgs.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to our IRC bouncer&lt;/li&gt;&#xA;&#x9;&lt;/ul&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;In order to continue using our premium services, you need to purchase another year:&#xA;&#x9;&lt;a href=&#34;https://auth.pico.sh/checkout/user-a&#34;&gt;purchase pico+&lt;/a&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</content>
35     <link href="https://pico.sh" rel="alternate"></link>
36-    <summary type="html">Your pico+ membership is going to expire on 2021-08-16 14:30:45</summary>
37+    <summary type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ 1-week expiration notification!&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;pico+ 1-week expiration notification!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;Your &lt;code&gt;pico+&lt;/code&gt; membership will expire on &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If your pico+ membership expires then we will:&#xA;&#xA;&#x9;&lt;ul&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to &lt;a href=&#34;https&#34;&gt;https://tuns.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;reject new sites being created for &lt;a href=&#34;https://pgs.sh&#34;&gt;pgs.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to our IRC bouncer&lt;/li&gt;&#xA;&#x9;&lt;/ul&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;In order to continue using our premium services, you need to purchase another year:&#xA;&#x9;&lt;a href=&#34;https://auth.pico.sh/checkout/user-a&#34;&gt;purchase pico+&lt;/a&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</summary>
38     <author>
39       <name>team pico</name>
40     </author>
41@@ -137,9 +137,9 @@ successfully added pico+ user
42     <title>pico+ 1-day expiration notice</title>
43     <updated>2021-08-14T14:30:45Z</updated>
44     <id>1628951445</id>
45-    <content type="html">Your pico+ membership is going to expire on 2021-08-16 14:30:45</content>
46+    <content type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ 1-day expiration notification!&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;pico+ 1-day expiration notification!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;Your &lt;code&gt;pico+&lt;/code&gt; membership will expire on &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If your pico+ membership expires then we will:&#xA;&#xA;&#x9;&lt;ul&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to &lt;a href=&#34;https&#34;&gt;https://tuns.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;reject new sites being created for &lt;a href=&#34;https://pgs.sh&#34;&gt;pgs.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to our IRC bouncer&lt;/li&gt;&#xA;&#x9;&lt;/ul&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;In order to continue using our premium services, you need to purchase another year:&#xA;&#x9;&lt;a href=&#34;https://auth.pico.sh/checkout/user-a&#34;&gt;purchase pico+&lt;/a&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</content>
47     <link href="https://pico.sh" rel="alternate"></link>
48-    <summary type="html">Your pico+ membership is going to expire on 2021-08-16 14:30:45</summary>
49+    <summary type="html">&#xA;&lt;html&gt;&#xA;&#x9;&lt;head&gt;&#xA;&#x9;&#x9;&lt;title&gt;pico+ 1-day expiration notification!&lt;/title&gt;&#xA;&#x9;&#x9;&lt;style&gt;&#xA;&#x9;&#x9;&#x9;code {&#xA;&#x9;&#x9;&#x9;&#x9;background-color: #ddd;&#xA;&#x9;&#x9;&#x9;&#x9;border-radius: 5px;&#xA;&#x9;&#x9;&#x9;&#x9;padding: 1px 3px;&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;&lt;/style&gt;&#xA;&#x9;&lt;/head&gt;&#xA;&#x9;&lt;body&gt;&#xA;&#x9;&#x9;&lt;h1&gt;pico+ 1-day expiration notification!&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#x9;Your &lt;code&gt;pico+&lt;/code&gt; membership will expire on &lt;strong&gt;2021-08-16&lt;/strong&gt;.&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If your pico+ membership expires then we will:&#xA;&#xA;&#x9;&lt;ul&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to &lt;a href=&#34;https&#34;&gt;https://tuns.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;reject new sites being created for &lt;a href=&#34;https://pgs.sh&#34;&gt;pgs.sh&lt;/a&gt;&lt;/li&gt;&#xA;&#x9;&#x9;&lt;li&gt;revoke access to our IRC bouncer&lt;/li&gt;&#xA;&#x9;&lt;/ul&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;In order to continue using our premium services, you need to purchase another year:&#xA;&#x9;&lt;a href=&#34;https://auth.pico.sh/checkout/user-a&#34;&gt;purchase pico+&lt;/a&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#x9;If you have any questions, please do not hesitate to &lt;a href=&#34;https://pico.sh/contact&#34;&gt;contact us&lt;/a&gt;.&#xA;&lt;/p&gt;&#xA;&#x9;&lt;/body&gt;&#xA;&lt;/html&gt;&#xA;</summary>
50     <author>
51       <name>team pico</name>
52     </author>
M pkg/apps/auth/api.go
+1, -1
1@@ -340,7 +340,7 @@ func rssHandler(apiConfig *shared.ApiConfig) http.HandlerFunc {
2 			return
3 		}
4 
5-		feed, err := shared.UserFeed(apiConfig.Dbpool, user.ID, apiToken)
6+		feed, err := shared.UserFeed(apiConfig.Dbpool, user, apiToken)
7 		if err != nil {
8 			return
9 		}
M pkg/apps/pgs/cli.go
+1, -1
1@@ -19,7 +19,7 @@ import (
2 )
3 
4 func NewTabWriter(out io.Writer) *tabwriter.Writer {
5-	return tabwriter.NewWriter(out, 0, 0, 1, ' ', tabwriter.TabIndent)
6+	return tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.TabIndent)
7 }
8 
9 func projectTable(sesh io.Writer, projects []*db.Project) {
M pkg/shared/feed.go
+74, -12
  1@@ -8,10 +8,71 @@ import (
  2 	"github.com/picosh/pico/pkg/db"
  3 )
  4 
  5-func UserFeed(me db.DB, userID, token string) (*feeds.Feed, error) {
  6+func genUserFeedTmpl(title, msg string) string {
  7+	return fmt.Sprintf(`
  8+<html>
  9+	<head>
 10+		<title>%s</title>
 11+		<style>
 12+			code {
 13+				background-color: #ddd;
 14+				border-radius: 5px;
 15+				padding: 1px 3px;
 16+			}
 17+		</style>
 18+	</head>
 19+	<body>
 20+		%s
 21+	</body>
 22+</html>
 23+`, title, msg)
 24+}
 25+
 26+func PicoPlusFeed(expiration time.Time) string {
 27+	msg := fmt.Sprintf(`<h1>Thanks for joining <code>pico+</code>!</h1>
 28+<p>
 29+	You now have access to all our premium services until <strong>%s</strong>.
 30+</p>
 31+<p>
 32+	We will send you <code>pico+</code> expiration notifications through this RSS feed.
 33+	Go to <a href="https://pico.sh/getting-started#next-steps">pico.sh/getting-started#next-steps</a>
 34+	to start using our services.
 35+</p>
 36+<p>
 37+	If you have any questions, please do not hesitate to <a href="https://pico.sh/contact">contact us</a>.
 38+</p>`, expiration.Format(time.DateOnly))
 39+	return genUserFeedTmpl("pico+ activated", msg)
 40+}
 41+
 42+func PicoPlusExpirationFeed(expiration time.Time, txt string, plusLink string) string {
 43+	title := fmt.Sprintf("pico+ %s expiration notification!", txt)
 44+	msg := fmt.Sprintf(`<h1>%s</h1>
 45+<p>
 46+	Your <code>pico+</code> membership will expire on <strong>%s</strong>.
 47+</p>
 48+<p>
 49+	If your pico+ membership expires then we will:
 50+
 51+	<ul>
 52+		<li>revoke access to <a href="https">https://tuns.sh</a></li>
 53+		<li>reject new sites being created for <a href="https://pgs.sh">pgs.sh</a></li>
 54+		<li>revoke access to our IRC bouncer</li>
 55+	</ul>
 56+</p>
 57+<p>
 58+	In order to continue using our premium services, you need to purchase another year:
 59+	<a href="%s">purchase pico+</a>
 60+</p>
 61+<p>
 62+	If you have any questions, please do not hesitate to <a href="https://pico.sh/contact">contact us</a>.
 63+</p>`, title, expiration.Format(time.DateOnly), plusLink)
 64+	return genUserFeedTmpl(title, msg)
 65+}
 66+
 67+func UserFeed(me db.DB, user *db.User, token string) (*feeds.Feed, error) {
 68 	var err error
 69 	if token == "" {
 70-		token, err = me.UpsertToken(userID, "pico-rss")
 71+		token, err = me.UpsertToken(user.ID, "pico-rss")
 72 		if err != nil {
 73 			return nil, err
 74 		}
 75@@ -28,14 +89,14 @@ func UserFeed(me db.DB, userID, token string) (*feeds.Feed, error) {
 76 	var feedItems []*feeds.Item
 77 
 78 	now := time.Now()
 79-	ff, err := me.FindFeatureForUser(userID, "plus")
 80+	ff, err := me.FindFeatureForUser(user.ID, "plus")
 81 	if err != nil {
 82 		// still want to send an empty feed
 83 	} else {
 84 		createdAt := ff.CreatedAt
 85-		createdAtStr := createdAt.Format("2006-01-02 15:04:05")
 86+		createdAtStr := createdAt.Format(time.RFC3339)
 87 		id := fmt.Sprintf("pico-plus-activated-%d", createdAt.Unix())
 88-		content := `Thanks for joining pico+! You now have access to all our premium services for exactly one year.  We will send you pico+ expiration notifications through this RSS feed.  Go to <a href="https://pico.sh/getting-started#next-steps">pico.sh/getting-started#next-steps</a> to start using our services.`
 89+		content := PicoPlusFeed(*ff.ExpiresAt)
 90 		plus := &feeds.Item{
 91 			Id:          id,
 92 			Title:       fmt.Sprintf("pico+ membership activated on %s", createdAtStr),
 93@@ -49,19 +110,19 @@ func UserFeed(me db.DB, userID, token string) (*feeds.Feed, error) {
 94 		feedItems = append(feedItems, plus)
 95 
 96 		oneMonthWarning := ff.ExpiresAt.AddDate(0, -1, 0)
 97-		mo := genFeedItem(now, *ff.ExpiresAt, oneMonthWarning, "1-month")
 98+		mo := genFeedItem(user.Name, now, *ff.ExpiresAt, oneMonthWarning, "1-month")
 99 		if mo != nil {
100 			feedItems = append(feedItems, mo)
101 		}
102 
103 		oneWeekWarning := ff.ExpiresAt.AddDate(0, 0, -7)
104-		wk := genFeedItem(now, *ff.ExpiresAt, oneWeekWarning, "1-week")
105+		wk := genFeedItem(user.Name, now, *ff.ExpiresAt, oneWeekWarning, "1-week")
106 		if wk != nil {
107 			feedItems = append(feedItems, wk)
108 		}
109 
110 		oneDayWarning := ff.ExpiresAt.AddDate(0, 0, -2)
111-		day := genFeedItem(now, *ff.ExpiresAt, oneDayWarning, "1-day")
112+		day := genFeedItem(user.Name, now, *ff.ExpiresAt, oneDayWarning, "1-day")
113 		if day != nil {
114 			feedItems = append(feedItems, day)
115 		}
116@@ -71,11 +132,12 @@ func UserFeed(me db.DB, userID, token string) (*feeds.Feed, error) {
117 	return feed, nil
118 }
119 
120-func genFeedItem(now time.Time, expiresAt time.Time, warning time.Time, txt string) *feeds.Item {
121+func genFeedItem(userName string, now time.Time, expiresAt time.Time, warning time.Time, txt string) *feeds.Item {
122 	if now.After(warning) {
123-		content := fmt.Sprintf(
124-			"Your pico+ membership is going to expire on %s",
125-			expiresAt.Format("2006-01-02 15:04:05"),
126+		content := PicoPlusExpirationFeed(
127+			expiresAt,
128+			txt,
129+			"https://auth.pico.sh/checkout/"+userName,
130 		)
131 		return &feeds.Item{
132 			Id:          fmt.Sprintf("%d", warning.Unix()),