diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go
index 46668bc..1295114 100644
--- a/cmd/web/handlers_guestbook.go
+++ b/cmd/web/handlers_guestbook.go
@@ -151,11 +151,12 @@ func (app *application) getGuestbookCommentsSerialized(w http.ResponseWriter, r
if !website.Guestbook.Settings.IsVisible || !website.Guestbook.Settings.AllowRemoteHostAccess {
app.clientError(w, http.StatusForbidden)
}
- comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
+ comments, err := app.guestbookComments.GetAllSerialized(website.Guestbook.ID)
if err != nil {
app.serverError(w, r, err)
return
}
+
b, err := json.Marshal(comments)
w.Write(b)
}
@@ -244,6 +245,54 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt
http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook", slug), http.StatusSeeOther)
}
+func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, r *http.Request) {
+ slug := r.PathValue("id")
+ website, err := app.websites.Get(slugToShortId(slug))
+ if err != nil {
+ if errors.Is(err, models.ErrNoRecord) {
+ http.NotFound(w, r)
+ } else {
+ app.serverError(w, r, err)
+ }
+ return
+ }
+
+ if !website.Guestbook.CanComment() {
+ app.clientError(w, http.StatusForbidden)
+ return
+ }
+
+ var form forms.CommentCreateForm
+ err = app.decodePostForm(r, &form)
+ if err != nil {
+ app.clientError(w, http.StatusBadRequest)
+ return
+ }
+
+ form.CheckField(validator.NotBlank(form.AuthorName), "authorName", "This field cannot be blank")
+ form.CheckField(validator.MaxChars(form.AuthorName, 256), "authorName", "This field cannot be more than 256 characters long")
+ form.CheckField(validator.MaxChars(form.AuthorEmail, 256), "authorEmail", "This field cannot be more than 256 characters long")
+ form.CheckField(validator.MaxChars(form.AuthorSite, 256), "authorSite", "This field cannot be more than 256 characters long")
+ form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank")
+ // TODO: Add optional field filled with window.location.href
+ // If it is populated, use as redirect URL
+ // Else redirect to homepage as stored in the website struct
+ redirectUrl := r.Header.Get("Referer")
+
+ if !form.Valid() {
+ views.GuestbookCommentCreateRemoteErrorView(redirectUrl, "Invalid Input").Render(r.Context(), w)
+ return
+ }
+
+ shortId := app.createShortId()
+ _, err = app.guestbookComments.Insert(shortId, website.Guestbook.ID, 0, form.AuthorName, form.AuthorEmail, form.AuthorSite, form.Content, "", true)
+ if err != nil {
+ app.serverError(w, r, err)
+ return
+ }
+ views.GuestbookCommentCreateRemoteSuccessView(redirectUrl).Render(r.Context(), w)
+}
+
func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("id")
website, err := app.websites.Get(slugToShortId(slug))
diff --git a/cmd/web/routes.go b/cmd/web/routes.go
index f6b6375..f9e768e 100644
--- a/cmd/web/routes.go
+++ b/cmd/web/routes.go
@@ -20,6 +20,7 @@ func (app *application) routes() http.Handler {
mux.Handle("/{$}", dynamic.ThenFunc(app.home))
mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook))
mux.Handle("GET /websites/{id}/guestbook/comments", withCors.ThenFunc(app.getGuestbookCommentsSerialized))
+ mux.Handle("POST /websites/{id}/guestbook/comments/create/remote", standard.ThenFunc(app.postGuestbookCommentCreateRemote))
mux.Handle("GET /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.getGuestbookCommentCreate))
mux.Handle("POST /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate))
mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister))
diff --git a/internal/models/guestbookcomment.go b/internal/models/guestbookcomment.go
index 4a7203b..d44de26 100644
--- a/internal/models/guestbookcomment.go
+++ b/internal/models/guestbookcomment.go
@@ -20,6 +20,12 @@ type GuestbookComment struct {
IsPublished bool
}
+type GuestbookCommentSerialized struct {
+ AuthorName string
+ CommentText string
+ Created string
+}
+
type GuestbookCommentModel struct {
DB *sql.DB
}
@@ -28,6 +34,7 @@ type GuestbookCommentModelInterface interface {
Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error)
Get(shortId uint64) (GuestbookComment, error)
GetAll(guestbookId int64) ([]GuestbookComment, error)
+ GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error)
GetDeleted(guestbookId int64) ([]GuestbookComment, error)
GetUnpublished(guestbookId int64) ([]GuestbookComment, error)
UpdateComment(comment *GuestbookComment) error
@@ -93,6 +100,30 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, e
return comments, nil
}
+func (m *GuestbookCommentModel) GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) {
+ stmt := `SELECT AuthorName, CommentText, Created
+ FROM guestbook_comments
+ WHERE GuestbookId = ? AND IsPublished = TRUE AND DELETED IS NULL
+ ORDER BY Created DESC`
+ rows, err := m.DB.Query(stmt, guestbookId)
+ if err != nil {
+ return nil, err
+ }
+ var comments []GuestbookCommentSerialized
+ for rows.Next() {
+ var c GuestbookCommentSerialized
+ err = rows.Scan(&c.AuthorName, &c.CommentText, &c.Created)
+ if err != nil {
+ return nil, err
+ }
+ comments = append(comments, c)
+ }
+ if err = rows.Err(); err != nil {
+ return nil, err
+ }
+ return comments, nil
+}
+
func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]GuestbookComment, error) {
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
CommentText, PageUrl, Created, IsPublished, Deleted
diff --git a/ui/views/common.templ b/ui/views/common.templ
index 13b5dd4..a9111f1 100644
--- a/ui/views/common.templ
+++ b/ui/views/common.templ
@@ -6,87 +6,85 @@ import "fmt"
import "strings"
type CommonData struct {
- CurrentYear int
- Flash string
- IsAuthenticated bool
- CSRFToken string
- CurrentUser *models.User
- IsHtmx bool
+ CurrentYear int
+ Flash string
+ IsAuthenticated bool
+ CSRFToken string
+ CurrentUser *models.User
+ IsHtmx bool
}
func shortIdToSlug(shortId uint64) string {
- return strconv.FormatUint(shortId, 36)
+ return strconv.FormatUint(shortId, 36)
}
func slugToShortId(slug string) uint64 {
- id, _ := strconv.ParseUint(slug, 36, 64)
- return id
+ id, _ := strconv.ParseUint(slug, 36, 64)
+ return id
}
func externalUrl(url string) string {
- if !strings.HasPrefix(url, "http") {
- return "http://" + url
- }
- return url
+ if !strings.HasPrefix(url, "http") {
+ return "http://" + url
+ }
+ return url
}
templ commonHeader() {
- webweav.ing
- webweav.ing
+
{ c.CommentText } @@ -232,3 +238,35 @@ templ AllGuestbooksView(data CommonData, websites []models.Website) {
+ An error occurred while posting comment. { err }. Redirecting. +
++ Redirect +
+ + +} + +templ GuestbookCommentCreateRemoteSuccessView(url string) { + + + + + ++ Comment successfully posted. Redirecting. +
++ Redirect +
+ + +} diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index 3c55d7c..5b12c21 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -542,60 +542,89 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 127, Col: 26} + if c.AuthorSite != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 129, Col: 89} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 131, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 136, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "