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