implement finalize and unsubscribe endpoints
This commit is contained in:
parent
ec4aac24ab
commit
9c3ea8dd80
@ -32,18 +32,24 @@ func mailingListSubscribeHandler(ml mailinglist.MailingList) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mailingListFinalizeHandler(ml mailinglist.MailingList) http.Handler {
|
func mailingListFinalizeHandler(ml mailinglist.MailingList) http.Handler {
|
||||||
|
var errInvalidSubToken = errors.New("invalid subToken")
|
||||||
|
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
subToken := r.PostFormValue("subToken")
|
subToken := r.PostFormValue("subToken")
|
||||||
if l := len(subToken); l == 0 || l > 128 {
|
if l := len(subToken); l == 0 || l > 128 {
|
||||||
badRequest(rw, r, errors.New("invalid subToken"))
|
badRequest(rw, r, errInvalidSubToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ml.FinalizeSubscription(subToken)
|
err := ml.FinalizeSubscription(subToken)
|
||||||
if errors.Is(err, mailinglist.ErrNotFound) ||
|
|
||||||
errors.Is(err, mailinglist.ErrAlreadyVerified) {
|
if errors.Is(err, mailinglist.ErrNotFound) {
|
||||||
badRequest(rw, r, err)
|
badRequest(rw, r, errInvalidSubToken)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
} else if errors.Is(err, mailinglist.ErrAlreadyVerified) {
|
||||||
|
// no problem
|
||||||
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
internalServerError(rw, r, err)
|
internalServerError(rw, r, err)
|
||||||
return
|
return
|
||||||
@ -54,17 +60,21 @@ func mailingListFinalizeHandler(ml mailinglist.MailingList) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mailingListUnsubscribeHandler(ml mailinglist.MailingList) http.Handler {
|
func mailingListUnsubscribeHandler(ml mailinglist.MailingList) http.Handler {
|
||||||
|
var errInvalidUnsubToken = errors.New("invalid unsubToken")
|
||||||
|
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
unsubToken := r.PostFormValue("unsubToken")
|
unsubToken := r.PostFormValue("unsubToken")
|
||||||
if l := len(unsubToken); l == 0 || l > 128 {
|
if l := len(unsubToken); l == 0 || l > 128 {
|
||||||
badRequest(rw, r, errors.New("invalid unsubToken"))
|
badRequest(rw, r, errInvalidUnsubToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ml.Unsubscribe(unsubToken)
|
err := ml.Unsubscribe(unsubToken)
|
||||||
|
|
||||||
if errors.Is(err, mailinglist.ErrNotFound) {
|
if errors.Is(err, mailinglist.ErrNotFound) {
|
||||||
badRequest(rw, r, err)
|
badRequest(rw, r, errInvalidUnsubToken)
|
||||||
return
|
return
|
||||||
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
internalServerError(rw, r, err)
|
internalServerError(rw, r, err)
|
||||||
return
|
return
|
||||||
|
@ -27,7 +27,7 @@ func main() {
|
|||||||
|
|
||||||
logger := mlog.NewLogger(nil)
|
logger := mlog.NewLogger(nil)
|
||||||
|
|
||||||
hostname := flag.String("hostname", "localhost:4000", "Hostname to advertise this server as")
|
publicURLStr := flag.String("public-url", "http://localhost:4000", "URL this service is accessible at")
|
||||||
listenAddr := flag.String("listen-addr", ":4000", "Address to listen for HTTP requests on")
|
listenAddr := flag.String("listen-addr", ":4000", "Address to listen for HTTP requests on")
|
||||||
dataDir := flag.String("data-dir", ".", "Directory to use for long term storage")
|
dataDir := flag.String("data-dir", ".", "Directory to use for long term storage")
|
||||||
|
|
||||||
@ -55,6 +55,11 @@ func main() {
|
|||||||
logger.Fatal(context.Background(), "-ml-smtp-auth is required")
|
logger.Fatal(context.Background(), "-ml-smtp-auth is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicURL, err := url.Parse(*publicURLStr)
|
||||||
|
if err != nil {
|
||||||
|
loggerFatalErr(context.Background(), logger, "parsing -public-url", err)
|
||||||
|
}
|
||||||
|
|
||||||
var staticProxyURL *url.URL
|
var staticProxyURL *url.URL
|
||||||
if *staticProxyURLStr != "" {
|
if *staticProxyURLStr != "" {
|
||||||
var err error
|
var err error
|
||||||
@ -79,7 +84,7 @@ func main() {
|
|||||||
// initialization
|
// initialization
|
||||||
|
|
||||||
ctx := mctx.Annotate(context.Background(),
|
ctx := mctx.Annotate(context.Background(),
|
||||||
"hostname", *hostname,
|
"publicURL", publicURL.String(),
|
||||||
"listenAddr", *listenAddr,
|
"listenAddr", *listenAddr,
|
||||||
"dataDir", *dataDir,
|
"dataDir", *dataDir,
|
||||||
"powTarget", fmt.Sprintf("%x", powTarget),
|
"powTarget", fmt.Sprintf("%x", powTarget),
|
||||||
@ -124,8 +129,8 @@ func main() {
|
|||||||
Store: mlStore,
|
Store: mlStore,
|
||||||
Mailer: mailer,
|
Mailer: mailer,
|
||||||
Clock: clock,
|
Clock: clock,
|
||||||
FinalizeSubURL: *hostname + "/mailinglist/finalize.html",
|
FinalizeSubURL: path.Join(publicURL.String(), "/mailinglist/finalize.html"),
|
||||||
UnsubURL: *hostname + "/mailinglist/unsubscribe.html",
|
UnsubURL: path.Join(publicURL.String(), "/mailinglist/unsubscribe.html"),
|
||||||
})
|
})
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
// ErrNotFound is used to indicate an email could not be found in the
|
// ErrNotFound is used to indicate an email could not be found in the
|
||||||
// database.
|
// database.
|
||||||
ErrNotFound = errors.New("no record for given email found")
|
ErrNotFound = errors.New("no record found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmailIterator will iterate through a sequence of emails, returning the next
|
// EmailIterator will iterate through a sequence of emails, returning the next
|
||||||
|
53
static/src/mailinglist/finalize.md
Normal file
53
static/src/mailinglist/finalize.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: ""
|
||||||
|
nofollow: true
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#result.success { color: green; }
|
||||||
|
#result.fail { color: red; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<span id="result"></span>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const resultSpan = document.getElementById("result");
|
||||||
|
|
||||||
|
function setErr(errStr) {
|
||||||
|
resultSpan.className = "fail";
|
||||||
|
resultSpan.innerHTML = errStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const subToken = urlParams.get('subToken');
|
||||||
|
|
||||||
|
if (!subToken) {
|
||||||
|
setErr("No subscription token provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalizeForm = new FormData();
|
||||||
|
finalizeForm.append('subToken', subToken);
|
||||||
|
|
||||||
|
const finalizeReq = new Request('/api/mailinglist/finalize', {
|
||||||
|
method: 'POST',
|
||||||
|
body: finalizeForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch(finalizeReq)
|
||||||
|
.then(response => response.json());
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
setErr(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSpan.className = "success";
|
||||||
|
resultSpan.innerHTML = "Your email subscription has been finalized! Please go on about your day.";
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
54
static/src/mailinglist/unsubscribe.md
Normal file
54
static/src/mailinglist/unsubscribe.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: ""
|
||||||
|
nofollow: true
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#result.success { color: green; }
|
||||||
|
#result.fail { color: red; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<span id="result"></span>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const resultSpan = document.getElementById("result");
|
||||||
|
|
||||||
|
function setErr(errStr) {
|
||||||
|
resultSpan.className = "fail";
|
||||||
|
resultSpan.innerHTML = errStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const unsubToken = urlParams.get('unsubToken');
|
||||||
|
|
||||||
|
if (!unsubToken) {
|
||||||
|
setErr("No unsubscribe token provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsubscribeForm = new FormData();
|
||||||
|
unsubscribeForm.append('unsubToken', unsubToken);
|
||||||
|
|
||||||
|
const unsubscribeReq = new Request('/api/mailinglist/unsubscribe', {
|
||||||
|
method: 'POST',
|
||||||
|
body: unsubscribeForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch(unsubscribeReq)
|
||||||
|
.then(response => response.json());
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
setErr(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSpan.className = "success";
|
||||||
|
resultSpan.innerHTML = "You have been unsubscribed! Please go on about your day.";
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user