api,frontend: add unsubscribe
This commit is contained in:
parent
60a9f2cc15
commit
e1c94ecf15
12
api/email.go
12
api/email.go
@ -5,10 +5,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type email struct {
|
type email struct {
|
||||||
Email string
|
Email string `json:"email"`
|
||||||
UnsubscribeSecretHex string
|
UnsubscribeSecretHex string `json:"unsubscribeSecretHex"`
|
||||||
LastEmailNotificationDate time.Time
|
LastEmailNotificationDate time.Time `json:"lastEmailNotificationDate"`
|
||||||
PendingEmails int
|
PendingEmails int `json:"-"`
|
||||||
SendReplyNotifications bool
|
SendReplyNotifications bool `json:"sendReplyNotifications"`
|
||||||
SendModeratorNotifications bool
|
SendModeratorNotifications bool `json:"sendModeratorNotifications"`
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import ()
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
func emailGet(em string) (email, error) {
|
func emailGet(em string) (email, error) {
|
||||||
statement := `
|
statement := `
|
||||||
@ -18,3 +20,40 @@ func emailGet(em string) (email, error) {
|
|||||||
|
|
||||||
return e, nil
|
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
39
api/email_update.go
Normal 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})
|
||||||
|
}
|
@ -43,3 +43,4 @@ var errorInvalidConfigValue = errors.New("Invalid config value.")
|
|||||||
var errorNewOwnerForbidden = errors.New("New user registrations are forbidden and closed.")
|
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 errorThreadLocked = errors.New("This thread is locked. You cannot add new comments.")
|
||||||
var errorDatabaseMigration = errors.New("Encountered error applying database migration.")
|
var errorDatabaseMigration = errors.New("Encountered error applying database migration.")
|
||||||
|
var errorNoSuchUnsubscribeSecretHex = errors.New("Invalid unsubscribe link.")
|
||||||
|
@ -27,7 +27,9 @@ func apiRouterInit(router *mux.Router) error {
|
|||||||
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
|
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
|
||||||
router.HandleFunc("/api/commenter/login", commenterLoginHandler).Methods("POST")
|
router.HandleFunc("/api/commenter/login", commenterLoginHandler).Methods("POST")
|
||||||
router.HandleFunc("/api/commenter/self", commenterSelfHandler).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/redirect", googleRedirectHandler).Methods("GET")
|
||||||
router.HandleFunc("/api/oauth/google/callback", googleCallbackHandler).Methods("GET")
|
router.HandleFunc("/api/oauth/google/callback", googleCallbackHandler).Methods("GET")
|
||||||
|
@ -98,6 +98,7 @@ func staticRouterInit(router *mux.Router) error {
|
|||||||
"/reset-password",
|
"/reset-password",
|
||||||
"/signup",
|
"/signup",
|
||||||
"/confirm-email",
|
"/confirm-email",
|
||||||
|
"/unsubscribe",
|
||||||
"/dashboard",
|
"/dashboard",
|
||||||
"/logout",
|
"/logout",
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,12 @@ const jsCompileMap = {
|
|||||||
"js/logout.js"
|
"js/logout.js"
|
||||||
],
|
],
|
||||||
"js/commento.js": ["js/commento.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 () {
|
gulp.task("scss-devel", function () {
|
||||||
|
54
frontend/js/unsubscribe.js
Normal file
54
frontend/js/unsubscribe.js
Normal 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));
|
49
frontend/sass/unsubscribe-main.scss
Normal file
49
frontend/sass/unsubscribe-main.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
frontend/sass/unsubscribe.scss
Normal file
4
frontend/sass/unsubscribe.scss
Normal 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
49
frontend/unsubscribe.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user