api,frontend: add unsubscribe

This commit is contained in:
Adhityaa Chandrasekar 2019-02-18 16:30:54 -05:00
parent 60a9f2cc15
commit e1c94ecf15
11 changed files with 252 additions and 8 deletions

View File

@ -5,10 +5,10 @@ import (
)
type email struct {
Email string
UnsubscribeSecretHex string
LastEmailNotificationDate time.Time
PendingEmails int
SendReplyNotifications bool
SendModeratorNotifications bool
Email string `json:"email"`
UnsubscribeSecretHex string `json:"unsubscribeSecretHex"`
LastEmailNotificationDate time.Time `json:"lastEmailNotificationDate"`
PendingEmails int `json:"-"`
SendReplyNotifications bool `json:"sendReplyNotifications"`
SendModeratorNotifications bool `json:"sendModeratorNotifications"`
}

View File

@ -1,6 +1,8 @@
package main
import ()
import (
"net/http"
)
func emailGet(em string) (email, error) {
statement := `
@ -18,3 +20,40 @@ func emailGet(em string) (email, error) {
return e, nil
}
func emailGetByUnsubscribeSecretHex(unsubscribeSecretHex string) (email, error) {
statement := `
SELECT email, unsubscribeSecretHex, lastEmailNotificationDate, pendingEmails, sendReplyNotifications, sendModeratorNotifications
FROM emails
WHERE unsubscribeSecretHex = $1;
`
row := db.QueryRow(statement, unsubscribeSecretHex)
e := email{}
if err := row.Scan(&e.Email, &e.UnsubscribeSecretHex, &e.LastEmailNotificationDate, &e.PendingEmails, &e.SendReplyNotifications, &e.SendModeratorNotifications); err != nil {
// TODO: is this the only error?
return e, errorNoSuchUnsubscribeSecretHex
}
return e, nil
}
func emailGetHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
UnsubscribeSecretHex *string `json:"unsubscribeSecretHex"`
}
var x request
if err := bodyUnmarshal(r, &x); err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
e, err := emailGetByUnsubscribeSecretHex(*x.UnsubscribeSecretHex)
if err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
bodyMarshal(w, response{"success": true, "email": e})
}

39
api/email_update.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"net/http"
)
func emailUpdate(e email) error {
statement := `
UPDATE emails
SET sendReplyNotifications = $3, sendModeratorNotifications = $4
WHERE email = $1 AND unsubscribeSecretHex = $2;
`
_, err := db.Exec(statement, e.Email, e.UnsubscribeSecretHex, e.SendReplyNotifications, e.SendModeratorNotifications)
if err != nil {
logger.Errorf("error updating email: %v", err)
return errorInternal
}
return nil
}
func emailUpdateHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
Email *email `json:"email"`
}
var x request
if err := bodyUnmarshal(r, &x); err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
if err := emailUpdate(*x.Email); err != nil {
bodyMarshal(w, response{"success": true, "message": err.Error()})
return
}
bodyMarshal(w, response{"success": true})
}

View File

@ -43,3 +43,4 @@ var errorInvalidConfigValue = errors.New("Invalid config value.")
var errorNewOwnerForbidden = errors.New("New user registrations are forbidden and closed.")
var errorThreadLocked = errors.New("This thread is locked. You cannot add new comments.")
var errorDatabaseMigration = errors.New("Encountered error applying database migration.")
var errorNoSuchUnsubscribeSecretHex = errors.New("Invalid unsubscribe link.")

View File

@ -27,7 +27,9 @@ func apiRouterInit(router *mux.Router) error {
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
router.HandleFunc("/api/commenter/login", commenterLoginHandler).Methods("POST")
router.HandleFunc("/api/commenter/self", commenterSelfHandler).Methods("POST")
router.HandleFunc("/api/commenter/unsubscribe", commenterSelfHandler).Methods("GET")
router.HandleFunc("/api/email/get", emailGetHandler).Methods("POST")
router.HandleFunc("/api/email/update", emailUpdateHandler).Methods("POST")
router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET")
router.HandleFunc("/api/oauth/google/callback", googleCallbackHandler).Methods("GET")

View File

@ -98,6 +98,7 @@ func staticRouterInit(router *mux.Router) error {
"/reset-password",
"/signup",
"/confirm-email",
"/unsubscribe",
"/dashboard",
"/logout",
}

View File

@ -74,6 +74,12 @@ const jsCompileMap = {
"js/logout.js"
],
"js/commento.js": ["js/commento.js"],
"js/unsubscribe.js": [
"js/constants.js",
"js/utils.js",
"js/http.js",
"js/unsubscribe.js",
],
};
gulp.task("scss-devel", function () {

View File

@ -0,0 +1,54 @@
(function (global, document) {
"use strict";
(document);
var e;
// Update the email records.
global.emailUpdate = function() {
$(".err").text("");
$(".msg").text("");
e.sendModeratorNotifications = $("#moderator").is(":checked");
e.sendReplyNotifications = $("#reply").is(":checked");
var json = {
"email": e,
};
global.buttonDisable("#save-button");
global.post(global.origin + "/api/email/update", json, function(resp) {
global.buttonEnable("#save-button");
if (!resp.success) {
$(".err").text(resp.message);
return;
}
$(".msg").text("Successfully updated!");
});
}
// Checks the unsubscribeSecretHex token to retrieve current settings.
global.emailGet = function() {
$(".err").text("");
$(".msg").text("");
var json = {
"unsubscribeSecretHex": global.paramGet("unsubscribeSecretHex"),
};
global.post(global.origin + "/api/email/get", json, function(resp) {
$(".loading").hide();
if (!resp.success) {
$(".err").text(resp.message);
return;
}
e = resp.email;
$("#email").text(e.email);
$("#moderator").prop("checked", e.sendModeratorNotifications);
$("#reply").prop("checked", e.sendReplyNotifications);
$(".checkboxes").attr("style", "");
});
};
} (window.commento, document));

View File

@ -0,0 +1,49 @@
@import "checkbox.scss";
@import "button.scss";
.unsubscribe-container {
margin-top: 72px;
display: flex;
justify-content: center;
.unsubscribe {
max-width: 600px;
width: 100%;
.loading {
font-size: 32px;
font-weight: bold;
text-align: center;
}
.err {
color: $red-6;
text-align: center;
font-weight: bold;
}
.msg {
color: $green-6;
font-weight: bold;
margin-bottom: 12px;
height: 20px;
}
.checkboxes {
.email {
margin-bottom: 32px;
font-size: 21px;
font-weight: bold;
}
.email::before {
content: "Changing email notifications for ";
font-weight: normal;
}
.row {
margin-bottom: 24px;
}
}
}
}

View File

@ -0,0 +1,4 @@
@import "common-main.scss";
@import "navbar-main.scss";
@import "unsubscribe-main.scss";
@import "tomorrow.scss";

49
frontend/unsubscribe.html Normal file
View File

@ -0,0 +1,49 @@
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
<script src="[[[.CdnPrefix]]]/js/unsubscribe.js"></script>
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/unsubscribe.css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
<title>Commento: Unsubscribe</title>
</head>
<div class="navbar">
<a href="[[[.Origin]]]/" class="navbar-item navbar-logo-text"><img src="[[[.CdnPrefix]]]/images/logo.svg" class="navbar-logo">Commento</a>
</div>
<script>
window.onload = function() {
window.commento.emailGet();
};
</script>
<div class="unsubscribe-container">
<div class="unsubscribe">
<div class="err"></div>
<div class="loading">Loading...</div>
<div class="checkboxes" style="display: none">
<div class="email" id="email"></div>
<div class="row no-border commento-round-check indent" id="mod-checkbox">
<input type="checkbox" id="moderator" value="moderator">
<label for="moderator">Email me moderator-related notifications</label>
<div class="pitch">
If you're a moderator of any domain, depending on each domain's moderator email policy, you may receive emails when a new comment is posted or when a comment is pending moderation. Unchecking this override those settings for you.
</div>
</div>
<div class="row no-border commento-round-check indent" id="reply-checkbox">
<input type="checkbox" id="reply" value="reply">
<label for="reply">Email me when someone replies to my comment</label>
<div class="pitch">
When someone replies to your comment, you can choose to receive a notification. These emails will be batched and delayed (by 10 minutes) so that your inbox won't get overwhelmed.
</div>
</div>
<div class="msg"></div>
<button id="save-button" class="button" onclick="window.commento.emailUpdate()">Update</button>
</div>
</div>
</div>
[[[.Footer]]]
</html>