package handlers import ( "encoding/json" "fmt" "justguestbook/internal/guestbook" "justguestbook/pkg/justcaptcha" "justguestbook/pkg/server" "log" "net/http" "strconv" "strings" ) type GuestbookHandlers struct { owner string password string anonymousName string defaultPageSize int64 db guestbook.Guestbook captchaAddr string } func New(owner, password, anonymousName string, defaultPageSize int64, guestbook guestbook.Guestbook, captchaAddr string) *GuestbookHandlers { return &GuestbookHandlers{ owner: owner, password: password, anonymousName: anonymousName, defaultPageSize: defaultPageSize, db: guestbook, captchaAddr: captchaAddr} } func (h *GuestbookHandlers) Entries(w http.ResponseWriter, r *http.Request) { var err error var page_num int64 = 1 if r.URL.Query().Get("p") != "" { page_num, err = strconv.ParseInt(r.URL.Query().Get("p"), 10, 64) if err != nil { page_num = 1 } } var page_size int64 = h.defaultPageSize if r.URL.Query().Get("ps") != "" { page_size, err = strconv.ParseInt(r.URL.Query().Get("ps"), 10, 64) if err != nil { page_size = h.defaultPageSize } } entries, err := h.db.Entries(page_num, page_size) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Println("failed to retrieve entries:", err) return } guestbookEntries := struct { Owner string `json:"owner"` Entries []*guestbook.Entry `json:"entries"` }{ Owner: h.owner, Entries: entries} w.Header().Add("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(&guestbookEntries); err != nil { log.Println("failed to encode entries:", err) http.Error(w, fmt.Sprintln("failed to encode entries:", err.Error()), http.StatusInternalServerError) } } func (h *GuestbookHandlers) New(w http.ResponseWriter, r *http.Request) { var entry *guestbook.Entry var err error if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { r.ParseForm() if r.FormValue("captcha_id") == "" { w.WriteHeader(http.StatusForbidden) return } solved, err := justcaptcha.CheckCaptcha(r.FormValue("captcha_id"), h.captchaAddr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Println("justcaptcha:", err) return } if !solved { w.WriteHeader(http.StatusForbidden) return } name := r.FormValue("name") if name == "" { name = h.anonymousName } entry, err = guestbook.NewEntry(name, r.FormValue("message"), r.FormValue("website"), len(r.FormValue("hide_website")) != 0) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } } else if r.Header.Get("Content-Type") == "application/json" { cid := struct { CaptchaID string `json:"captcha_id"` }{} if err := json.NewDecoder(r.Body).Decode(&cid); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } solved, err := justcaptcha.CheckCaptcha(cid.CaptchaID, h.captchaAddr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Println("justcaptcha:", err) return } if !solved { w.WriteHeader(http.StatusForbidden) return } if err := json.NewDecoder(r.Body).Decode(entry); err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } } err = h.db.NewEntry(entry) if err != nil { http.Error(w, entry.Message, http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } func (h *GuestbookHandlers) Reply(w http.ResponseWriter, r *http.Request) { var reply *guestbook.Reply if r.Header.Get("X-Password") != h.password { w.WriteHeader(http.StatusForbidden) return } id, err := strconv.ParseInt(server.GetURLParam(r, "entry"), 10, 64) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { r.ParseForm() reply, err = guestbook.NewReply(id, r.FormValue("reply")) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } } else if r.Header.Get("Content-Type") == "application/json" { if err := json.NewDecoder(r.Body).Decode(reply); err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } } if err := h.db.NewReply(reply); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } func (h *GuestbookHandlers) Update(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Password") != h.password { w.WriteHeader(http.StatusForbidden) return } entryID, err := strconv.ParseInt(server.GetURLParam(r, "entry"), 10, 64) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } if strings.HasSuffix(r.URL.Path, "reply") { rp := guestbook.Reply{} json.NewDecoder(r.Body).Decode(&rp) isCreated, err := h.db.UpdateReply(entryID, &rp) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if isCreated { w.WriteHeader(http.StatusCreated) } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(&rp) } else { et := guestbook.Entry{} json.NewDecoder(r.Body).Decode(&et) isCreated, err := h.db.UpdateEntry(entryID, &et) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if isCreated { w.WriteHeader(http.StatusCreated) } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(&et) } } func (h *GuestbookHandlers) Delete(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Password") != h.password { w.WriteHeader(http.StatusForbidden) return } entryID, err := strconv.ParseInt(server.GetURLParam(r, "entry"), 10, 64) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } if strings.HasSuffix(r.URL.Path, "reply") { if err := h.db.DeleteReply(entryID); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } else { if err := h.db.DeleteEntry(entryID); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } }