Break out some controllers and move mail to subpackage
Controllers moved into sub-packages: * Initial setup * Admin routes * Auth login/out routes * Contact
This commit is contained in:
parent
6d3de7da69
commit
eb1880d348
234
core/admin.go
234
core/admin.go
|
@ -1,234 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/kirsle/blog/core/internal/forms"
|
|
||||||
"github.com/kirsle/blog/core/internal/middleware/auth"
|
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
|
||||||
"github.com/kirsle/blog/core/internal/render"
|
|
||||||
"github.com/kirsle/blog/core/internal/responses"
|
|
||||||
"github.com/urfave/negroni"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AdminRoutes attaches the admin routes to the app.
|
|
||||||
func (b *Blog) AdminRoutes(r *mux.Router) {
|
|
||||||
adminRouter := mux.NewRouter().PathPrefix("/admin").Subrouter().StrictSlash(true)
|
|
||||||
adminRouter.HandleFunc("/", b.AdminHandler)
|
|
||||||
adminRouter.HandleFunc("/settings", b.SettingsHandler)
|
|
||||||
adminRouter.HandleFunc("/editor", b.EditorHandler)
|
|
||||||
// r.HandleFunc("/admin", b.AdminHandler)
|
|
||||||
r.PathPrefix("/admin").Handler(negroni.New(
|
|
||||||
negroni.HandlerFunc(auth.LoginRequired(b.MustLogin)),
|
|
||||||
negroni.Wrap(adminRouter),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminHandler is the admin landing page.
|
|
||||||
func (b *Blog) AdminHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
render.Template(w, r, "admin/index", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileTree holds information about files in the document roots.
|
|
||||||
type FileTree struct {
|
|
||||||
UserRoot bool // false = CoreRoot
|
|
||||||
Files []render.Filepath
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditorHandler lets you edit web pages from the frontend.
|
|
||||||
func (b *Blog) EditorHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Editing a page?
|
|
||||||
file := strings.Trim(r.FormValue("file"), "/")
|
|
||||||
if len(file) > 0 {
|
|
||||||
var (
|
|
||||||
fp string
|
|
||||||
fromCore = r.FormValue("from") == "core"
|
|
||||||
saving = r.FormValue("action") == ActionSave
|
|
||||||
deleting = r.FormValue("action") == ActionDelete
|
|
||||||
body = []byte{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Are they saving?
|
|
||||||
if saving {
|
|
||||||
fp = filepath.Join(b.UserRoot, file)
|
|
||||||
body = []byte(r.FormValue("body"))
|
|
||||||
err := ioutil.WriteFile(fp, body, 0644)
|
|
||||||
if err != nil {
|
|
||||||
responses.Flash(w, r, "Error saving: %s", err)
|
|
||||||
} else {
|
|
||||||
responses.FlashAndRedirect(w, r, "/admin/editor?file="+url.QueryEscape(file), "Page saved successfully!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if deleting {
|
|
||||||
fp = filepath.Join(b.UserRoot, file)
|
|
||||||
err := os.Remove(fp)
|
|
||||||
if err != nil {
|
|
||||||
responses.FlashAndRedirect(w, r, "/admin/editor", "Error deleting: %s", err)
|
|
||||||
} else {
|
|
||||||
responses.FlashAndRedirect(w, r, "/admin/editor", "Page deleted!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Where is the file from?
|
|
||||||
if fromCore {
|
|
||||||
fp = filepath.Join(b.DocumentRoot, file)
|
|
||||||
} else {
|
|
||||||
fp = filepath.Join(b.UserRoot, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the file. If not found, check from the core root.
|
|
||||||
f, err := os.Stat(fp)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
fp = filepath.Join(b.DocumentRoot, file)
|
|
||||||
fromCore = true
|
|
||||||
f, err = os.Stat(fp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it exists, load it.
|
|
||||||
if !os.IsNotExist(err) && !f.IsDir() {
|
|
||||||
body, err = ioutil.ReadFile(fp)
|
|
||||||
if err != nil {
|
|
||||||
responses.Flash(w, r, "Error reading %s: %s", fp, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default HTML boilerplate for .gohtml templates.
|
|
||||||
if len(body) == 0 && strings.HasSuffix(fp, ".gohtml") {
|
|
||||||
body = []byte("{{ define \"title\" }}Untitled Page{{ end }}\n" +
|
|
||||||
"{{ define \"content\" }}\n<h1>Untitled Page</h1>\n\n{{ end }}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"File": file,
|
|
||||||
"Path": fp,
|
|
||||||
"Body": string(body),
|
|
||||||
"FromCore": fromCore,
|
|
||||||
}
|
|
||||||
render.Template(w, r, "admin/editor", v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise listing the index view.
|
|
||||||
b.editorFileList(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// editorFileList handles the index view of /admin/editor.
|
|
||||||
func (b *Blog) editorFileList(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Listing the file tree?
|
|
||||||
trees := []FileTree{}
|
|
||||||
for i, root := range []string{b.UserRoot, b.DocumentRoot} {
|
|
||||||
tree := FileTree{
|
|
||||||
UserRoot: i == 0,
|
|
||||||
Files: []render.Filepath{},
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
|
||||||
abs, _ := filepath.Abs(path)
|
|
||||||
rel, _ := filepath.Rel(root, path)
|
|
||||||
|
|
||||||
// Skip hidden files and directories.
|
|
||||||
if f.IsDir() || rel == "." || strings.HasPrefix(rel, ".private") || strings.HasPrefix(rel, "admin/") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only text files.
|
|
||||||
ext := strings.ToLower(filepath.Ext(path))
|
|
||||||
okTypes := []string{
|
|
||||||
".html", ".gohtml", ".md", ".markdown", ".js", ".css", ".jsx",
|
|
||||||
}
|
|
||||||
ok := false
|
|
||||||
for _, ft := range okTypes {
|
|
||||||
if ext == ft {
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tree.Files = append(tree.Files, render.Filepath{
|
|
||||||
Absolute: abs,
|
|
||||||
Relative: rel,
|
|
||||||
Basename: filepath.Base(path),
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
trees = append(trees, tree)
|
|
||||||
}
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"FileTrees": trees,
|
|
||||||
}
|
|
||||||
render.Template(w, r, "admin/filelist", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingsHandler lets you configure the app from the frontend.
|
|
||||||
func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get the current settings.
|
|
||||||
settings, _ := settings.Load()
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"s": settings,
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
redisPort, _ := strconv.Atoi(r.FormValue("redis-port"))
|
|
||||||
redisDB, _ := strconv.Atoi(r.FormValue("redis-db"))
|
|
||||||
mailPort, _ := strconv.Atoi(r.FormValue("mail-port"))
|
|
||||||
form := &forms.Settings{
|
|
||||||
Title: r.FormValue("title"),
|
|
||||||
Description: r.FormValue("description"),
|
|
||||||
AdminEmail: r.FormValue("admin-email"),
|
|
||||||
URL: r.FormValue("url"),
|
|
||||||
RedisEnabled: len(r.FormValue("redis-enabled")) > 0,
|
|
||||||
RedisHost: r.FormValue("redis-host"),
|
|
||||||
RedisPort: redisPort,
|
|
||||||
RedisDB: redisDB,
|
|
||||||
RedisPrefix: r.FormValue("redis-prefix"),
|
|
||||||
MailEnabled: len(r.FormValue("mail-enabled")) > 0,
|
|
||||||
MailSender: r.FormValue("mail-sender"),
|
|
||||||
MailHost: r.FormValue("mail-host"),
|
|
||||||
MailPort: mailPort,
|
|
||||||
MailUsername: r.FormValue("mail-username"),
|
|
||||||
MailPassword: r.FormValue("mail-password"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy form values into the settings struct for display, in case of
|
|
||||||
// any validation errors.
|
|
||||||
settings.Site.Title = form.Title
|
|
||||||
settings.Site.Description = form.Description
|
|
||||||
settings.Site.AdminEmail = form.AdminEmail
|
|
||||||
settings.Site.URL = form.URL
|
|
||||||
settings.Redis.Enabled = form.RedisEnabled
|
|
||||||
settings.Redis.Host = form.RedisHost
|
|
||||||
settings.Redis.Port = form.RedisPort
|
|
||||||
settings.Redis.DB = form.RedisDB
|
|
||||||
settings.Redis.Prefix = form.RedisPrefix
|
|
||||||
settings.Mail.Enabled = form.MailEnabled
|
|
||||||
settings.Mail.Sender = form.MailSender
|
|
||||||
settings.Mail.Host = form.MailHost
|
|
||||||
settings.Mail.Port = form.MailPort
|
|
||||||
settings.Mail.Username = form.MailUsername
|
|
||||||
settings.Mail.Password = form.MailPassword
|
|
||||||
err := form.Validate()
|
|
||||||
if err != nil {
|
|
||||||
v["Error"] = err
|
|
||||||
} else {
|
|
||||||
// Save the settings.
|
|
||||||
settings.Save()
|
|
||||||
b.Configure()
|
|
||||||
|
|
||||||
responses.FlashAndReload(w, r, "Settings have been saved!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render.Template(w, r, "admin/settings", v)
|
|
||||||
}
|
|
|
@ -2,15 +2,14 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/kirsle/blog/core/internal/log"
|
"github.com/kirsle/blog/core/internal/log"
|
||||||
|
"github.com/kirsle/blog/core/internal/mail"
|
||||||
"github.com/kirsle/blog/core/internal/markdown"
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/middleware/auth"
|
"github.com/kirsle/blog/core/internal/middleware/auth"
|
||||||
"github.com/kirsle/blog/core/internal/models/comments"
|
"github.com/kirsle/blog/core/internal/models/comments"
|
||||||
|
@ -236,7 +235,7 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
responses.FlashAndRedirect(w, r, c.OriginURL, "Error posting comment: %s", err)
|
responses.FlashAndRedirect(w, r, c.OriginURL, "Error posting comment: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.NotifyComment(c)
|
mail.NotifyComment(c)
|
||||||
|
|
||||||
// Are they subscribing to future comments?
|
// Are they subscribing to future comments?
|
||||||
if c.Subscribe && len(c.Email) > 0 {
|
if c.Subscribe && len(c.Email) > 0 {
|
||||||
|
@ -266,14 +265,13 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// SubscriptionHandler to opt out of subscriptions.
|
// SubscriptionHandler to opt out of subscriptions.
|
||||||
func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
|
||||||
// POST to unsubscribe from all threads.
|
// POST to unsubscribe from all threads.
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
email := r.FormValue("email")
|
email := r.FormValue("email")
|
||||||
if email == "" {
|
if email == "" {
|
||||||
err = errors.New("email address is required to unsubscribe from comment threads")
|
b.BadRequest(w, r, "email address is required to unsubscribe from comment threads")
|
||||||
} else if _, err := mail.ParseAddress(email); err != nil {
|
} else if _, err := mail.ParseAddress(email); err != nil {
|
||||||
err = errors.New("invalid email address")
|
b.BadRequest(w, r, "invalid email address")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := comments.LoadMailingList()
|
m := comments.LoadMailingList()
|
||||||
|
@ -294,9 +292,7 @@ func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Template(w, r, "comments/subscription.gohtml", map[string]error{
|
render.Template(w, r, "comments/subscription.gohtml", nil)
|
||||||
"Error": err,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuickDeleteHandler allows the admin to quickly delete spam without logging in.
|
// QuickDeleteHandler allows the admin to quickly delete spam without logging in.
|
||||||
|
|
19
core/core.go
19
core/core.go
|
@ -7,6 +7,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kirsle/blog/core/internal/controllers/admin"
|
||||||
|
"github.com/kirsle/blog/core/internal/controllers/authctl"
|
||||||
|
"github.com/kirsle/blog/core/internal/controllers/contact"
|
||||||
|
"github.com/kirsle/blog/core/internal/controllers/setup"
|
||||||
"github.com/kirsle/blog/core/internal/log"
|
"github.com/kirsle/blog/core/internal/log"
|
||||||
"github.com/kirsle/blog/core/internal/markdown"
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/middleware"
|
"github.com/kirsle/blog/core/internal/middleware"
|
||||||
|
@ -16,6 +20,7 @@ import (
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
"github.com/kirsle/blog/core/internal/models/users"
|
"github.com/kirsle/blog/core/internal/models/users"
|
||||||
"github.com/kirsle/blog/core/internal/render"
|
"github.com/kirsle/blog/core/internal/render"
|
||||||
|
"github.com/kirsle/blog/core/internal/responses"
|
||||||
"github.com/kirsle/blog/core/internal/sessions"
|
"github.com/kirsle/blog/core/internal/sessions"
|
||||||
"github.com/kirsle/blog/jsondb"
|
"github.com/kirsle/blog/jsondb"
|
||||||
"github.com/kirsle/blog/jsondb/caches"
|
"github.com/kirsle/blog/jsondb/caches"
|
||||||
|
@ -105,10 +110,10 @@ func (b *Blog) Configure() {
|
||||||
func (b *Blog) SetupHTTP() {
|
func (b *Blog) SetupHTTP() {
|
||||||
// Initialize the router.
|
// Initialize the router.
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/initial-setup", b.SetupHandler)
|
setup.Register(r)
|
||||||
b.AuthRoutes(r)
|
authctl.Register(r)
|
||||||
b.AdminRoutes(r)
|
admin.Register(r, b.MustLogin)
|
||||||
b.ContactRoutes(r)
|
contact.Register(r, b.Error)
|
||||||
b.BlogRoutes(r)
|
b.BlogRoutes(r)
|
||||||
b.CommentRoutes(r)
|
b.CommentRoutes(r)
|
||||||
|
|
||||||
|
@ -137,3 +142,9 @@ func (b *Blog) ListenAndServe(address string) {
|
||||||
log.Info("Listening on %s", address)
|
log.Info("Listening on %s", address)
|
||||||
http.ListenAndServe(address, b.n)
|
http.ListenAndServe(address, b.n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustLogin handles errors from the LoginRequired middleware by redirecting
|
||||||
|
// the user to the login page.
|
||||||
|
func (b *Blog) MustLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
responses.Redirect(w, "/login?next="+r.URL.Path)
|
||||||
|
}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/kirsle/blog/core/internal/forms"
|
|
||||||
"github.com/kirsle/blog/core/internal/log"
|
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
|
||||||
"github.com/kirsle/blog/core/internal/models/users"
|
|
||||||
"github.com/kirsle/blog/core/internal/render"
|
|
||||||
"github.com/kirsle/blog/core/internal/responses"
|
|
||||||
"github.com/kirsle/blog/core/internal/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetupHandler is the initial blog setup route.
|
|
||||||
func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
form := &forms.Setup{}
|
|
||||||
vars := map[string]interface{}{
|
|
||||||
"Form": form,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject if we're already set up.
|
|
||||||
s, _ := settings.Load()
|
|
||||||
if s.Initialized {
|
|
||||||
responses.FlashAndRedirect(w, r, "/", "This website has already been configured.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
form.ParseForm(r)
|
|
||||||
err := form.Validate()
|
|
||||||
if err != nil {
|
|
||||||
vars["Error"] = err
|
|
||||||
} else {
|
|
||||||
// Save the site config.
|
|
||||||
log.Info("Creating default website config file")
|
|
||||||
s := settings.Defaults()
|
|
||||||
s.Save()
|
|
||||||
|
|
||||||
// Re-initialize the cookie store with the new secret key.
|
|
||||||
sessions.SetSecretKey([]byte(s.Security.SecretKey))
|
|
||||||
|
|
||||||
log.Info("Creating admin account %s", form.Username)
|
|
||||||
user := &users.User{
|
|
||||||
Username: form.Username,
|
|
||||||
Password: form.Password,
|
|
||||||
Admin: true,
|
|
||||||
Name: "Administrator",
|
|
||||||
}
|
|
||||||
err := users.Create(user)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error: %v", err)
|
|
||||||
vars["Error"] = err
|
|
||||||
}
|
|
||||||
|
|
||||||
// All set!
|
|
||||||
b.Login(w, r, user)
|
|
||||||
responses.FlashAndRedirect(w, r, "/admin", "Admin user created and logged in.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render.Template(w, r, "initial-setup", vars)
|
|
||||||
}
|
|
27
core/internal/controllers/admin/admin.go
Normal file
27
core/internal/controllers/admin/admin.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kirsle/blog/core/internal/middleware/auth"
|
||||||
|
"github.com/kirsle/blog/core/internal/render"
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register the initial setup routes.
|
||||||
|
func Register(r *mux.Router, authErrorFunc http.HandlerFunc) {
|
||||||
|
adminRouter := mux.NewRouter().PathPrefix("/admin").Subrouter().StrictSlash(true)
|
||||||
|
adminRouter.HandleFunc("/", indexHandler)
|
||||||
|
adminRouter.HandleFunc("/settings", settingsHandler)
|
||||||
|
adminRouter.HandleFunc("/editor", editorHandler)
|
||||||
|
|
||||||
|
r.PathPrefix("/admin").Handler(negroni.New(
|
||||||
|
negroni.HandlerFunc(auth.LoginRequired(authErrorFunc)),
|
||||||
|
negroni.Wrap(adminRouter),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Template(w, r, "admin/index", nil)
|
||||||
|
}
|
147
core/internal/controllers/admin/editor.go
Normal file
147
core/internal/controllers/admin/editor.go
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/core/internal/render"
|
||||||
|
"github.com/kirsle/blog/core/internal/responses"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileTree holds information about files in the document roots.
|
||||||
|
type FileTree struct {
|
||||||
|
UserRoot bool // false = CoreRoot
|
||||||
|
Files []render.Filepath
|
||||||
|
}
|
||||||
|
|
||||||
|
func editorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Editing a page?
|
||||||
|
file := strings.Trim(r.FormValue("file"), "/")
|
||||||
|
if len(file) > 0 {
|
||||||
|
var (
|
||||||
|
fp string
|
||||||
|
fromCore = r.FormValue("from") == "core"
|
||||||
|
saving = r.FormValue("action") == "save"
|
||||||
|
deleting = r.FormValue("action") == "delete"
|
||||||
|
body = []byte{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Are they saving?
|
||||||
|
if saving {
|
||||||
|
fp = filepath.Join(*render.UserRoot, file)
|
||||||
|
body = []byte(r.FormValue("body"))
|
||||||
|
err := ioutil.WriteFile(fp, body, 0644)
|
||||||
|
if err != nil {
|
||||||
|
responses.Flash(w, r, "Error saving: %s", err)
|
||||||
|
} else {
|
||||||
|
responses.FlashAndRedirect(w, r, "/admin/editor?file="+url.QueryEscape(file), "Page saved successfully!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if deleting {
|
||||||
|
fp = filepath.Join(*render.UserRoot, file)
|
||||||
|
err := os.Remove(fp)
|
||||||
|
if err != nil {
|
||||||
|
responses.FlashAndRedirect(w, r, "/admin/editor", "Error deleting: %s", err)
|
||||||
|
} else {
|
||||||
|
responses.FlashAndRedirect(w, r, "/admin/editor", "Page deleted!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Where is the file from?
|
||||||
|
if fromCore {
|
||||||
|
fp = filepath.Join(*render.DocumentRoot, file)
|
||||||
|
} else {
|
||||||
|
fp = filepath.Join(*render.UserRoot, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the file. If not found, check from the core root.
|
||||||
|
f, err := os.Stat(fp)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
fp = filepath.Join(*render.DocumentRoot, file)
|
||||||
|
fromCore = true
|
||||||
|
f, err = os.Stat(fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it exists, load it.
|
||||||
|
if !os.IsNotExist(err) && !f.IsDir() {
|
||||||
|
body, err = ioutil.ReadFile(fp)
|
||||||
|
if err != nil {
|
||||||
|
responses.Flash(w, r, "Error reading %s: %s", fp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default HTML boilerplate for .gohtml templates.
|
||||||
|
if len(body) == 0 && strings.HasSuffix(fp, ".gohtml") {
|
||||||
|
body = []byte("{{ define \"title\" }}Untitled Page{{ end }}\n" +
|
||||||
|
"{{ define \"content\" }}\n<h1>Untitled Page</h1>\n\n{{ end }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v := map[string]interface{}{
|
||||||
|
"File": file,
|
||||||
|
"Path": fp,
|
||||||
|
"Body": string(body),
|
||||||
|
"FromCore": fromCore,
|
||||||
|
}
|
||||||
|
render.Template(w, r, "admin/editor", v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise listing the index view.
|
||||||
|
editorFileList(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// editorFileList handles the index view of /admin/editor.
|
||||||
|
func editorFileList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Listing the file tree?
|
||||||
|
trees := []FileTree{}
|
||||||
|
for i, root := range []string{*render.UserRoot, *render.DocumentRoot} {
|
||||||
|
tree := FileTree{
|
||||||
|
UserRoot: i == 0,
|
||||||
|
Files: []render.Filepath{},
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||||
|
abs, _ := filepath.Abs(path)
|
||||||
|
rel, _ := filepath.Rel(root, path)
|
||||||
|
|
||||||
|
// Skip hidden files and directories.
|
||||||
|
if f.IsDir() || rel == "." || strings.HasPrefix(rel, ".private") || strings.HasPrefix(rel, "admin/") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only text files.
|
||||||
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
|
okTypes := []string{
|
||||||
|
".html", ".gohtml", ".md", ".markdown", ".js", ".css", ".jsx",
|
||||||
|
}
|
||||||
|
ok := false
|
||||||
|
for _, ft := range okTypes {
|
||||||
|
if ext == ft {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.Files = append(tree.Files, render.Filepath{
|
||||||
|
Absolute: abs,
|
||||||
|
Relative: rel,
|
||||||
|
Basename: filepath.Base(path),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
trees = append(trees, tree)
|
||||||
|
}
|
||||||
|
v := map[string]interface{}{
|
||||||
|
"FileTrees": trees,
|
||||||
|
}
|
||||||
|
render.Template(w, r, "admin/filelist", v)
|
||||||
|
}
|
72
core/internal/controllers/admin/settings.go
Normal file
72
core/internal/controllers/admin/settings.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/core/internal/forms"
|
||||||
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
|
"github.com/kirsle/blog/core/internal/render"
|
||||||
|
"github.com/kirsle/blog/core/internal/responses"
|
||||||
|
)
|
||||||
|
|
||||||
|
func settingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get the current settings.
|
||||||
|
settings, _ := settings.Load()
|
||||||
|
v := map[string]interface{}{
|
||||||
|
"s": settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
redisPort, _ := strconv.Atoi(r.FormValue("redis-port"))
|
||||||
|
redisDB, _ := strconv.Atoi(r.FormValue("redis-db"))
|
||||||
|
mailPort, _ := strconv.Atoi(r.FormValue("mail-port"))
|
||||||
|
form := &forms.Settings{
|
||||||
|
Title: r.FormValue("title"),
|
||||||
|
Description: r.FormValue("description"),
|
||||||
|
AdminEmail: r.FormValue("admin-email"),
|
||||||
|
URL: r.FormValue("url"),
|
||||||
|
RedisEnabled: len(r.FormValue("redis-enabled")) > 0,
|
||||||
|
RedisHost: r.FormValue("redis-host"),
|
||||||
|
RedisPort: redisPort,
|
||||||
|
RedisDB: redisDB,
|
||||||
|
RedisPrefix: r.FormValue("redis-prefix"),
|
||||||
|
MailEnabled: len(r.FormValue("mail-enabled")) > 0,
|
||||||
|
MailSender: r.FormValue("mail-sender"),
|
||||||
|
MailHost: r.FormValue("mail-host"),
|
||||||
|
MailPort: mailPort,
|
||||||
|
MailUsername: r.FormValue("mail-username"),
|
||||||
|
MailPassword: r.FormValue("mail-password"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy form values into the settings struct for display, in case of
|
||||||
|
// any validation errors.
|
||||||
|
settings.Site.Title = form.Title
|
||||||
|
settings.Site.Description = form.Description
|
||||||
|
settings.Site.AdminEmail = form.AdminEmail
|
||||||
|
settings.Site.URL = form.URL
|
||||||
|
settings.Redis.Enabled = form.RedisEnabled
|
||||||
|
settings.Redis.Host = form.RedisHost
|
||||||
|
settings.Redis.Port = form.RedisPort
|
||||||
|
settings.Redis.DB = form.RedisDB
|
||||||
|
settings.Redis.Prefix = form.RedisPrefix
|
||||||
|
settings.Mail.Enabled = form.MailEnabled
|
||||||
|
settings.Mail.Sender = form.MailSender
|
||||||
|
settings.Mail.Host = form.MailHost
|
||||||
|
settings.Mail.Port = form.MailPort
|
||||||
|
settings.Mail.Username = form.MailUsername
|
||||||
|
settings.Mail.Password = form.MailPassword
|
||||||
|
err := form.Validate()
|
||||||
|
if err != nil {
|
||||||
|
v["Error"] = err
|
||||||
|
} else {
|
||||||
|
// Save the settings.
|
||||||
|
settings.Save()
|
||||||
|
// b.Configure()
|
||||||
|
|
||||||
|
responses.FlashAndReload(w, r, "Settings have been saved!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render.Template(w, r, "admin/settings", v)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package core
|
package authctl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -14,33 +14,14 @@ import (
|
||||||
"github.com/kirsle/blog/core/internal/sessions"
|
"github.com/kirsle/blog/core/internal/sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthRoutes attaches the auth routes to the app.
|
// Register the initial setup routes.
|
||||||
func (b *Blog) AuthRoutes(r *mux.Router) {
|
func Register(r *mux.Router) {
|
||||||
r.HandleFunc("/login", b.LoginHandler)
|
r.HandleFunc("/login", loginHandler)
|
||||||
r.HandleFunc("/logout", b.LogoutHandler)
|
r.HandleFunc("/logout", logoutHandler)
|
||||||
r.HandleFunc("/account", b.AccountHandler)
|
r.HandleFunc("/account", accountHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustLogin handles errors from the LoginRequired middleware by redirecting
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// the user to the login page.
|
|
||||||
func (b *Blog) MustLogin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
responses.Redirect(w, "/login?next="+r.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login logs the browser in as the given user.
|
|
||||||
func (b *Blog) Login(w http.ResponseWriter, r *http.Request, u *users.User) error {
|
|
||||||
session, err := sessions.Store.Get(r, "session") // TODO session name
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
session.Values["logged-in"] = true
|
|
||||||
session.Values["user-id"] = u.ID
|
|
||||||
session.Save(r, w)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginHandler shows and handles the login page.
|
|
||||||
func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := map[string]interface{}{
|
vars := map[string]interface{}{
|
||||||
"Form": forms.Setup{},
|
"Form": forms.Setup{},
|
||||||
}
|
}
|
||||||
|
@ -70,7 +51,7 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
} else {
|
} else {
|
||||||
// Login OK!
|
// Login OK!
|
||||||
responses.Flash(w, r, "Login OK!")
|
responses.Flash(w, r, "Login OK!")
|
||||||
b.Login(w, r, user)
|
auth.Login(w, r, user)
|
||||||
|
|
||||||
// A next URL given? TODO: actually get to work
|
// A next URL given? TODO: actually get to work
|
||||||
log.Info("Redirect after login to: %s", nextURL)
|
log.Info("Redirect after login to: %s", nextURL)
|
||||||
|
@ -87,8 +68,7 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
render.Template(w, r, "login", vars)
|
render.Template(w, r, "login", vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogoutHandler logs the user out and redirects to the home page.
|
func logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func (b *Blog) LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
session, _ := sessions.Store.Get(r, "session")
|
session, _ := sessions.Store.Get(r, "session")
|
||||||
delete(session.Values, "logged-in")
|
delete(session.Values, "logged-in")
|
||||||
delete(session.Values, "user-id")
|
delete(session.Values, "user-id")
|
||||||
|
@ -96,8 +76,7 @@ func (b *Blog) LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
responses.Redirect(w, "/")
|
responses.Redirect(w, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountHandler shows the account settings page.
|
func accountHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func (b *Blog) AccountHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !auth.LoggedIn(r) {
|
if !auth.LoggedIn(r) {
|
||||||
responses.FlashAndRedirect(w, r, "/login?next=/account", "You must be logged in to do that!")
|
responses.FlashAndRedirect(w, r, "/login?next=/account", "You must be logged in to do that!")
|
||||||
return
|
return
|
|
@ -1,4 +1,4 @@
|
||||||
package core
|
package contact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -10,14 +10,15 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/kirsle/blog/core/internal/forms"
|
"github.com/kirsle/blog/core/internal/forms"
|
||||||
|
"github.com/kirsle/blog/core/internal/mail"
|
||||||
"github.com/kirsle/blog/core/internal/markdown"
|
"github.com/kirsle/blog/core/internal/markdown"
|
||||||
"github.com/kirsle/blog/core/internal/models/settings"
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
"github.com/kirsle/blog/core/internal/render"
|
"github.com/kirsle/blog/core/internal/render"
|
||||||
"github.com/kirsle/blog/core/internal/responses"
|
"github.com/kirsle/blog/core/internal/responses"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContactRoutes attaches the contact URL to the app.
|
// Register attaches the contact URL to the app.
|
||||||
func (b *Blog) ContactRoutes(r *mux.Router) {
|
func Register(r *mux.Router, onError func(http.ResponseWriter, *http.Request, string)) {
|
||||||
r.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) {
|
||||||
form := &forms.Contact{}
|
form := &forms.Contact{}
|
||||||
v := map[string]interface{}{
|
v := map[string]interface{}{
|
||||||
|
@ -27,13 +28,13 @@ func (b *Blog) ContactRoutes(r *mux.Router) {
|
||||||
// If there is no site admin, show an error.
|
// If there is no site admin, show an error.
|
||||||
cfg, err := settings.Load()
|
cfg, err := settings.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(w, r, "Error loading site configuration!")
|
onError(w, r, "Error loading site configuration!")
|
||||||
return
|
return
|
||||||
} else if cfg.Site.AdminEmail == "" {
|
} else if cfg.Site.AdminEmail == "" {
|
||||||
b.Error(w, r, "There is no admin email configured for this website!")
|
onError(w, r, "There is no admin email configured for this website!")
|
||||||
return
|
return
|
||||||
} else if !cfg.Mail.Enabled {
|
} else if !cfg.Mail.Enabled {
|
||||||
b.Error(w, r, "This website doesn't have an e-mail gateway configured.")
|
onError(w, r, "This website doesn't have an e-mail gateway configured.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ func (b *Blog) ContactRoutes(r *mux.Router) {
|
||||||
if err = form.Validate(); err != nil {
|
if err = form.Validate(); err != nil {
|
||||||
responses.Flash(w, r, err.Error())
|
responses.Flash(w, r, err.Error())
|
||||||
} else {
|
} else {
|
||||||
go b.SendEmail(Email{
|
go mail.SendEmail(mail.Email{
|
||||||
To: cfg.Site.AdminEmail,
|
To: cfg.Site.AdminEmail,
|
||||||
Admin: true,
|
Admin: true,
|
||||||
ReplyTo: form.Email,
|
ReplyTo: form.Email,
|
||||||
|
@ -57,7 +58,7 @@ func (b *Blog) ContactRoutes(r *mux.Router) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log it to disk, too.
|
// Log it to disk, too.
|
||||||
fh, err := os.OpenFile(filepath.Join(b.UserRoot, ".contact.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
fh, err := os.OpenFile(filepath.Join(*render.UserRoot, ".contact.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responses.Flash(w, r, "Error logging the message to disk: %s", err)
|
responses.Flash(w, r, "Error logging the message to disk: %s", err)
|
||||||
} else {
|
} else {
|
|
@ -1 +1,70 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kirsle/blog/core/internal/forms"
|
||||||
|
"github.com/kirsle/blog/core/internal/log"
|
||||||
|
"github.com/kirsle/blog/core/internal/middleware/auth"
|
||||||
|
"github.com/kirsle/blog/core/internal/models/settings"
|
||||||
|
"github.com/kirsle/blog/core/internal/models/users"
|
||||||
|
"github.com/kirsle/blog/core/internal/render"
|
||||||
|
"github.com/kirsle/blog/core/internal/responses"
|
||||||
|
"github.com/kirsle/blog/core/internal/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register the initial setup routes.
|
||||||
|
func Register(r *mux.Router) {
|
||||||
|
r.HandleFunc("/initial-setup", handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
form := &forms.Setup{}
|
||||||
|
vars := map[string]interface{}{
|
||||||
|
"Form": form,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject if we're already set up.
|
||||||
|
s, _ := settings.Load()
|
||||||
|
if s.Initialized {
|
||||||
|
responses.FlashAndRedirect(w, r, "/", "This website has already been configured.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
form.ParseForm(r)
|
||||||
|
err := form.Validate()
|
||||||
|
if err != nil {
|
||||||
|
vars["Error"] = err
|
||||||
|
} else {
|
||||||
|
// Save the site config.
|
||||||
|
log.Info("Creating default website config file")
|
||||||
|
s := settings.Defaults()
|
||||||
|
s.Save()
|
||||||
|
|
||||||
|
// Re-initialize the cookie store with the new secret key.
|
||||||
|
sessions.SetSecretKey([]byte(s.Security.SecretKey))
|
||||||
|
|
||||||
|
log.Info("Creating admin account %s", form.Username)
|
||||||
|
user := &users.User{
|
||||||
|
Username: form.Username,
|
||||||
|
Password: form.Password,
|
||||||
|
Admin: true,
|
||||||
|
Name: "Administrator",
|
||||||
|
}
|
||||||
|
err := users.Create(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error: %v", err)
|
||||||
|
vars["Error"] = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// All set!
|
||||||
|
auth.Login(w, r, user)
|
||||||
|
responses.FlashAndRedirect(w, r, "/admin", "Admin user created and logged in.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Template(w, r, "initial-setup", vars)
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package core
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ type Email struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendEmail sends an email.
|
// SendEmail sends an email.
|
||||||
func (b *Blog) SendEmail(email Email) {
|
func SendEmail(email Email) {
|
||||||
s, _ := settings.Load()
|
s, _ := settings.Load()
|
||||||
if !s.Mail.Enabled || s.Mail.Host == "" || s.Mail.Port == 0 || s.Mail.Sender == "" {
|
if !s.Mail.Enabled || s.Mail.Host == "" || s.Mail.Port == 0 || s.Mail.Sender == "" {
|
||||||
log.Info("Suppressing email: not completely configured")
|
log.Info("Suppressing email: not completely configured")
|
||||||
|
@ -83,11 +83,6 @@ func (b *Blog) SendEmail(email Email) {
|
||||||
m.AddAlternative("text/html", html.String())
|
m.AddAlternative("text/html", html.String())
|
||||||
|
|
||||||
d := gomail.NewDialer(s.Mail.Host, s.Mail.Port, s.Mail.Username, s.Mail.Password)
|
d := gomail.NewDialer(s.Mail.Host, s.Mail.Port, s.Mail.Username, s.Mail.Password)
|
||||||
if b.Debug {
|
|
||||||
d.TLSConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("SendEmail: %s (%s) to %s", email.Subject, email.Template, email.To)
|
log.Info("SendEmail: %s (%s) to %s", email.Subject, email.Template, email.To)
|
||||||
if err := d.DialAndSend(m); err != nil {
|
if err := d.DialAndSend(m); err != nil {
|
||||||
|
@ -96,7 +91,7 @@ func (b *Blog) SendEmail(email Email) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyComment sends notification emails about comments.
|
// NotifyComment sends notification emails about comments.
|
||||||
func (b *Blog) NotifyComment(c *comments.Comment) {
|
func NotifyComment(c *comments.Comment) {
|
||||||
s, _ := settings.Load()
|
s, _ := settings.Load()
|
||||||
if s.Site.URL == "" {
|
if s.Site.URL == "" {
|
||||||
log.Error("Can't send comment notification because the site URL is not configured")
|
log.Error("Can't send comment notification because the site URL is not configured")
|
||||||
|
@ -126,7 +121,7 @@ func (b *Blog) NotifyComment(c *comments.Comment) {
|
||||||
email.To = config.Site.AdminEmail
|
email.To = config.Site.AdminEmail
|
||||||
email.Admin = true
|
email.Admin = true
|
||||||
log.Info("Mail site admin '%s' about comment notification on '%s'", email.To, c.ThreadID)
|
log.Info("Mail site admin '%s' about comment notification on '%s'", email.To, c.ThreadID)
|
||||||
b.SendEmail(email)
|
SendEmail(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email the subscribers.
|
// Email the subscribers.
|
||||||
|
@ -143,6 +138,11 @@ func (b *Blog) NotifyComment(c *comments.Comment) {
|
||||||
url.QueryEscape(to),
|
url.QueryEscape(to),
|
||||||
)
|
)
|
||||||
log.Info("Mail subscriber '%s' about comment notification on '%s'", email.To, c.ThreadID)
|
log.Info("Mail subscriber '%s' about comment notification on '%s'", email.To, c.ThreadID)
|
||||||
b.SendEmail(email)
|
SendEmail(email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseAddress parses an email address.
|
||||||
|
func ParseAddress(addr string) (*mail.Address, error) {
|
||||||
|
return mail.ParseAddress(addr)
|
||||||
|
}
|
|
@ -26,6 +26,18 @@ func CurrentUser(r *http.Request) (*users.User, error) {
|
||||||
}, errors.New("not authenticated")
|
}, errors.New("not authenticated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login logs the browser in as the given user.
|
||||||
|
func Login(w http.ResponseWriter, r *http.Request, u *users.User) error {
|
||||||
|
session, err := sessions.Store.Get(r, "session") // TODO session name
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.Values["logged-in"] = true
|
||||||
|
session.Values["user-id"] = u.ID
|
||||||
|
session.Save(r, w)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoggedIn returns whether the current user is logged in to an account.
|
// LoggedIn returns whether the current user is logged in to an account.
|
||||||
func LoggedIn(r *http.Request) bool {
|
func LoggedIn(r *http.Request) bool {
|
||||||
session := sessions.Get(r)
|
session := sessions.Get(r)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1>Account Settings</h1>
|
<h1>Account Settings</h1>
|
||||||
|
|
||||||
|
{{ $form := .Data.Form }}
|
||||||
<form action="/account" method="POST">
|
<form action="/account" method="POST">
|
||||||
<input type="hidden" name="_csrf" value="{{ .CSRF }}">
|
<input type="hidden" name="_csrf" value="{{ .CSRF }}">
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="username"
|
name="username"
|
||||||
id="username"
|
id="username"
|
||||||
value="{{ .Form.Username }}"
|
value="{{ $form.Username }}"
|
||||||
placeholder="soandso">
|
placeholder="soandso">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -24,8 +25,8 @@
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="name"
|
name="name"
|
||||||
id="name"
|
id="name"
|
||||||
value="{{ .Form.Name }}"
|
value="{{ $form.Name }}"
|
||||||
placeholder="{{ or .Form.Username "Anonymous" }}">
|
placeholder="{{ or $form.Username "Anonymous" }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="email"
|
name="email"
|
||||||
id="email"
|
id="email"
|
||||||
value="{{ .Form.Email }}"
|
value="{{ $form.Email }}"
|
||||||
placeholder="name@domain.com">
|
placeholder="name@domain.com">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
administrator.
|
administrator.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
data={{ .Data }}
|
|
||||||
|
|
||||||
{{ $form := .Data.Form }}
|
{{ $form := .Data.Form }}
|
||||||
<form method="POST" action="/contact">
|
<form method="POST" action="/contact">
|
||||||
<input type="hidden" name="_csrf" value="{{ .CSRF }}">
|
<input type="hidden" name="_csrf" value="{{ .CSRF }}">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user