diff options
Diffstat (limited to 'system/admin/handlers.go')
-rw-r--r-- | system/admin/handlers.go | 269 |
1 files changed, 187 insertions, 82 deletions
diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 7b8bfae..c91db79 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -556,35 +556,20 @@ func forgotPasswordHandler(res http.ResponseWriter, req *http.Request) { email := strings.ToLower(req.FormValue("email")) if email == "" { res.WriteHeader(http.StatusBadRequest) - errView, err := Error400() - if err != nil { - return - } - - res.Write(errView) + log.Println("Failed account recovery. No email address submitted.") return } _, err = db.User(email) if err == db.ErrNoUserExists { res.WriteHeader(http.StatusBadRequest) - errView, err := Error400() - if err != nil { - return - } - - res.Write(errView) + log.Println("No user exists.", err) return } if err != db.ErrNoUserExists && err != nil { res.WriteHeader(http.StatusInternalServerError) - errView, err := Error500() - if err != nil { - return - } - - res.Write(errView) + log.Println("Error:", err) return } @@ -592,54 +577,48 @@ func forgotPasswordHandler(res http.ResponseWriter, req *http.Request) { key, err := db.SetRecoveryKey(email) if err != nil { res.WriteHeader(http.StatusInternalServerError) - errView, err := Error500() - if err != nil { - return - } + log.Println("Failed to set account recovery key.", err) + return + } - res.Write(errView) + domain, err := db.Config("domain") + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + log.Println("Failed to get domain from configuration.", err) return } - domain := db.ConfigCache("domain") body := fmt.Sprintf(` - There has been an account recovery request made for the user with email: - %s +There has been an account recovery request made for the user with email: +%s + +To recover your account, please go to http://%s/admin/recover/key and enter +this email address along with the following secret key: - To recover your account, please go to http://%s/admin/recover/key and enter - this email address along with the following secret key: - - %s +%s - If you did not make the request, ignore this message and your password - will remain as-is. +If you did not make the request, ignore this message and your password +will remain as-is. - Thank you, - Ponzu CMS at %s +Thank you, +Ponzu CMS at %s + +`, email, domain, key, domain) - `, email, domain, key, domain) msg := emailer.Message{ To: email, - From: fmt.Sprintf("Ponzu CMS <ponzu-cms@%s", domain), + From: fmt.Sprintf("ponzu@%s", domain), Subject: fmt.Sprintf("Account Recovery [%s]", domain), Body: body, } - /* + go func() { err = msg.Send() if err != nil { - res.WriteHeader(http.StatusInternalServerError) - errView, err := Error500() - if err != nil { - return - } - - res.Write(errView) - return + log.Println("Failed to send message to:", msg.To, "about", msg.Subject, "Error:", err) } - */ - fmt.Println(msg) + }() // redirect to /admin/recover/key and send email with key and URL http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/recover/key", http.StatusFound) @@ -662,33 +641,90 @@ func recoveryKeyHandler(res http.ResponseWriter, req *http.Request) { view, err := RecoveryKey() if err != nil { res.WriteHeader(http.StatusInternalServerError) - errView, err := Error500() - if err != nil { - return - } - - res.Write(errView) return } res.Write(view) case http.MethodPost: + err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB + if err != nil { + log.Println("Error parsing recovery key form:", err) + + res.WriteHeader(http.StatusInternalServerError) + res.Write([]byte("Error, please go back and try again.")) + return + } // check for email & key match + email := strings.ToLower(req.FormValue("email")) + key := req.FormValue("key") + + var actual string + if actual, err = db.RecoveryKey(email); err != nil || actual == "" { + log.Println("Error getting recovery key from database:", err) + + res.WriteHeader(http.StatusInternalServerError) + res.Write([]byte("Error, please go back and try again.")) + return + } + + if key != actual { + log.Println("Bad recovery key submitted:", key) + log.Println("Actual:", actual) + + res.WriteHeader(http.StatusBadRequest) + res.Write([]byte("Error, please go back and try again.")) + return + } // set user with new password + password := req.FormValue("password") + usr := &user.User{} + u, err := db.User(email) + if err != nil { + log.Println("Error finding user by email:", email, err) - // redirect to /admin/login + res.WriteHeader(http.StatusInternalServerError) + res.Write([]byte("Error, please go back and try again.")) + return + } - default: - res.WriteHeader(http.StatusMethodNotAllowed) - errView, err := Error405() + if u == nil { + log.Println("No user found with email:", email) + + res.WriteHeader(http.StatusBadRequest) + res.Write([]byte("Error, please go back and try again.")) + return + } + + err = json.Unmarshal(u, usr) if err != nil { + log.Println("Error decoding user from database:", err) + + res.WriteHeader(http.StatusInternalServerError) + res.Write([]byte("Error, please go back and try again.")) return } - res.Write(errView) + update := user.NewUser(email, password) + update.ID = usr.ID + + err = db.UpdateUser(usr, update) + if err != nil { + log.Println("Error updating user:", err) + + res.WriteHeader(http.StatusInternalServerError) + res.Write([]byte("Error, please go back and try again.")) + return + } + + // redirect to /admin/login + redir := req.URL.Scheme + req.URL.Host + "/admin/login" + http.Redirect(res, req, redir, http.StatusFound) + + default: + res.WriteHeader(http.StatusMethodNotAllowed) return } } @@ -697,7 +733,7 @@ func recoveryEditHandler(res http.ResponseWriter, req *http.Request) { } -func postsHandler(res http.ResponseWriter, req *http.Request) { +func contentsHandler(res http.ResponseWriter, req *http.Request) { q := req.URL.Query() t := q.Get("type") if t == "" { @@ -787,7 +823,7 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { Order: order, } - posts := db.Query(t+"_sorted", opts) + total, posts := db.Query(t+"__sorted", opts) b := &bytes.Buffer{} html := `<div class="col s9 card"> @@ -824,20 +860,25 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { </script> </div> </div> - <form class="col s4" action="/admin/posts/search" method="get"> + <form class="col s4" action="/admin/contents/search" method="get"> <div class="input-field post-search inline"> <label class="active">Search:</label> <i class="right material-icons search-icon">search</i> <input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/> <input type="hidden" name="type" value="` + t + `" /> + <input type="hidden" name="status" value="` + status + `" /> </div> </form> </div>` if hasExt { if status == "" { - q.Add("status", "public") + q.Set("status", "public") } + // always start from top of results when changing public/pending + q.Del("count") + q.Del("offset") + q.Set("status", "public") publicURL := req.URL.Path + "?" + q.Encode() @@ -868,8 +909,8 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { } case "pending": - // get _pending posts of type t from the db - posts = db.Query(t+"_pending", opts) + // get __pending posts of type t from the db + _, posts = db.Query(t+"__pending", opts) html += `<div class="row externalable"> <span class="description">Status:</span> @@ -911,7 +952,56 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { html += `<ul class="posts row">` - b.Write([]byte(`</ul></div></div>`)) + b.Write([]byte(`</ul>`)) + + statusDisabled := "disabled" + prevStatus := "" + nextStatus := "" + // total may be less than 10 (default count), so reset count to match total + if total < count { + count = total + } + // nothing previous to current list + if offset == 0 { + prevStatus = statusDisabled + } + // nothing after current list + if (offset+1)*count >= total { + nextStatus = statusDisabled + } + + // set up pagination values + urlFmt := req.URL.Path + "?count=%d&offset=%d&&order=%s&status=%s&type=%s" + prevURL := fmt.Sprintf(urlFmt, count, offset-1, order, status, t) + nextURL := fmt.Sprintf(urlFmt, count, offset+1, order, status, t) + start := 1 + count*offset + end := start + count - 1 + + if total < end { + end = total + } + + pagination := fmt.Sprintf(` + <ul class="pagination row"> + <li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_left</i></a></li> + <li class="col s8">%d to %d of %d</li> + <li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_right</i></a></li> + </ul> + `, prevStatus, prevURL, start, end, total, nextStatus, nextURL) + + // show indicator that a collection of items will be listed implicitly, but + // that none are created yet + if total < 1 { + pagination = ` + <ul class="pagination row"> + <li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_left</i></a></li> + <li class="col s8">0 to 0 of 0</li> + <li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_right</i></a></li> + </ul> + ` + } + + b.Write([]byte(pagination + `</div></div>`)) script := ` <script> @@ -923,6 +1013,13 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { } }); }); + + // disable link from being clicked if parent is 'disabled' + $(function() { + $('ul.pagination li.disabled a').on('click', function(e) { + e.preventDefault(); + }); + }); </script> ` @@ -942,7 +1039,7 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { // adminPostListItem is a helper to create the li containing a post. // p is the asserted post as an Editable, t is the Type of the post. -// specifier is passed to append a name to a namespace like _pending +// specifier is passed to append a name to a namespace like __pending func adminPostListItem(e editor.Editable, typeName, status string) []byte { s, ok := e.(editor.Sortable) if !ok { @@ -970,12 +1067,12 @@ func adminPostListItem(e editor.Editable, typeName, status string) []byte { case "public", "": status = "" default: - status = "_" + status + status = "__" + status } post := ` <li class="col s12"> - <a href="/admin/edit?type=` + typeName + `&status=` + strings.TrimPrefix(status, "_") + `&id=` + cid + `">` + e.ContentName() + `</a> + <a href="/admin/edit?type=` + typeName + `&status=` + strings.TrimPrefix(status, "__") + `&id=` + cid + `">` + i.String() + `</a> <span class="post-detail">Updated: ` + updatedTime + `</span> <span class="publish-date right">` + publishTime + `</span> @@ -989,7 +1086,7 @@ func adminPostListItem(e editor.Editable, typeName, status string) []byte { return []byte(post) } -func approvePostHandler(res http.ResponseWriter, req *http.Request) { +func approveContentHandler(res http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { res.WriteHeader(http.StatusMethodNotAllowed) errView, err := Error405() @@ -1014,8 +1111,8 @@ func approvePostHandler(res http.ResponseWriter, req *http.Request) { } t := req.FormValue("type") - if strings.Contains(t, "_") { - t = strings.Split(t, "_")[0] + if strings.Contains(t, "__") { + t = strings.Split(t, "__")[0] } post := content.Types[t]() @@ -1160,7 +1257,7 @@ func editHandler(res http.ResponseWriter, req *http.Request) { if i != "" { if status == "pending" { - t = t + "_pending" + t = t + "__pending" } data, err := db.Content(t + ":" + i) @@ -1275,7 +1372,7 @@ func editHandler(res http.ResponseWriter, req *http.Request) { } for name, urlPath := range urlPaths { - req.PostForm.Add(name, urlPath) + req.PostForm.Set(name, urlPath) } // check for any multi-value fields (ex. checkbox fields) @@ -1299,8 +1396,8 @@ func editHandler(res http.ResponseWriter, req *http.Request) { req.PostForm.Del(discardKey) } - if strings.Contains(t, "_") { - t = strings.Split(t, "_")[0] + if strings.Contains(t, "__") { + t = strings.Split(t, "__")[0] } p, ok := content.Types[t] @@ -1409,8 +1506,8 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) { } // catch specifier suffix from delete form value - if strings.Contains(t, "_") { - spec := strings.Split(t, "_") + if strings.Contains(t, "__") { + spec := strings.Split(t, "__") ct = spec[0] } @@ -1506,7 +1603,7 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) { } redir := strings.TrimSuffix(req.URL.Scheme+req.URL.Host+req.URL.Path, "/edit/delete") - redir = redir + "/posts?type=" + ct + redir = redir + "/contents?type=" + ct http.Redirect(res, req, redir, http.StatusFound) } @@ -1531,13 +1628,19 @@ func searchHandler(res http.ResponseWriter, req *http.Request) { q := req.URL.Query() t := q.Get("type") search := q.Get("q") + status := q.Get("status") + var specifier string if t == "" || search == "" { http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) return } - posts := db.ContentAll(t) + if status == "pending" { + specifier = "__" + status + } + + posts := db.ContentAll(t + specifier) b := &bytes.Buffer{} p := content.Types[t]().(editor.Editable) @@ -1545,11 +1648,13 @@ func searchHandler(res http.ResponseWriter, req *http.Request) { <div class="card-content"> <div class="row"> <div class="card-title col s7">` + t + ` Results</div> - <form class="col s5" action="/admin/posts/search" method="get"> + <form class="col s4" action="/admin/contents/search" method="get"> <div class="input-field post-search inline"> + <label class="active">Search:</label> <i class="right material-icons search-icon">search</i> - <input class="search" name="q" type="text" placeholder="Search for ` + t + ` content" class="search"/> + <input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/> <input type="hidden" name="type" value="` + t + `" /> + <input type="hidden" name="status" value="` + status + `" /> </div> </form> </div> @@ -1572,7 +1677,7 @@ func searchHandler(res http.ResponseWriter, req *http.Request) { continue } - post := adminPostListItem(p, t, "") + post := adminPostListItem(p, t, status) b.Write([]byte(post)) } |