finish conversion to templ, add website model

The primary model users will interact with is now Websites, which will have a single guestbook associated with it (though this is not enforced by the database).
This commit is contained in:
yequari 2025-03-22 13:13:13 -07:00
parent 11c0815676
commit c8ff7021c8
43 changed files with 1849 additions and 1494 deletions

3
.gitignore vendored
View File

@ -28,3 +28,6 @@ go.work
.air.toml
/tmp
tls/
test.db.old
.gitignore
.nvim/session

View File

@ -2,10 +2,14 @@ package main
import (
"net/http"
"git.32bit.cafe/32bitcafe/guestbook/ui/views"
)
func (app *application) home(w http.ResponseWriter, r *http.Request) {
data := app.newCommonData(r)
views.Home("Home", data).Render(r.Context(), w)
if app.isAuthenticated(r) {
http.Redirect(w, r, "/websites", http.StatusSeeOther)
return
}
views.Home("Home", app.newCommonData(r)).Render(r.Context(), w)
}

View File

@ -11,35 +11,6 @@ import (
"git.32bit.cafe/32bitcafe/guestbook/ui/views"
)
func (app *application) getGuestbookCreate(w http.ResponseWriter, r *http.Request) {
data := app.newCommonData(r)
views.GuestbookCreate("New Guestbook", data).Render(r.Context(), w)
}
func (app *application) postGuestbookCreate(w http.ResponseWriter, r *http.Request) {
userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId")
err := r.ParseForm()
if err != nil {
app.serverError(w, r, err)
return
}
siteUrl := r.Form.Get("siteurl")
shortId := app.createShortId()
_, err = app.guestbooks.Insert(shortId, siteUrl, userId)
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Put(r.Context(), "flash", "Guestbook successfully created!")
if r.Header.Get("HX-Request") == "true" {
w.Header().Add("HX-Trigger", "newGuestbook")
data := app.newTemplateData(r)
app.renderHTMX(w, r, http.StatusOK, "guestbookcreatebutton.part.html", data)
return
}
http.Redirect(w, r, fmt.Sprintf("/guestbooks/%s", shortIdToSlug(shortId)), http.StatusSeeOther)
}
func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request) {
userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId")
guestbooks, err := app.guestbooks.GetAll(userId)
@ -53,7 +24,7 @@ func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request)
func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(slug))
website, err := app.websites.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
@ -62,42 +33,18 @@ func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) {
}
return
}
comments, err := app.guestbookComments.GetAll(guestbook.ID)
comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
if err != nil {
app.serverError(w, r, err)
return
}
data := app.newCommonData(r)
views.GuestbookView("Guestbook", data, guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w)
}
func (app *application) getGuestbookDashboard(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
} else {
app.serverError(w, r, err)
}
return
}
user := app.getCurrentUser(r)
if user.ID != guestbook.UserId {
app.clientError(w, http.StatusUnauthorized)
}
comments, err := app.guestbookComments.GetAll(guestbook.ID)
if err != nil {
app.serverError(w, r, err)
return
}
data := app.newCommonData(r)
views.GuestbookDashboardView("Guestbook", data, guestbook, comments).Render(r.Context(), w)
views.GuestbookView("Guestbook", data, website, website.Guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w)
}
func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(slug))
website, err := app.websites.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
@ -106,19 +53,19 @@ func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Requ
}
return
}
comments, err := app.guestbookComments.GetAll(guestbook.ID)
comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
if err != nil {
app.serverError(w, r, err)
return
}
data := app.newCommonData(r)
views.GuestbookDashboardCommentsView("Comments", data, guestbook, comments).Render(r.Context(), w)
views.GuestbookDashboardCommentsView("Comments", data, website, website.Guestbook, comments).Render(r.Context(), w)
}
func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) {
// TODO: This will be the embeddable form
slug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(slug))
website, err := app.websites.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
@ -127,15 +74,15 @@ func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http
}
return
}
data := app.newTemplateData(r)
data.Guestbook = guestbook
data.Form = forms.CommentCreateForm{}
app.render(w, r, http.StatusOK, "commentcreate.view.tmpl.html", data)
data := app.newCommonData(r)
form := forms.CommentCreateForm{}
views.CreateGuestbookComment("New Comment", data, website, website.Guestbook, form).Render(r.Context(), w)
}
func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) {
guestbookSlug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(guestbookSlug))
slug := r.PathValue("id")
website, err := app.websites.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
@ -161,21 +108,24 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt
form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank")
if !form.Valid() {
data := app.newTemplateData(r)
data.Guestbook = guestbook
data.Form = form
app.render(w, r, http.StatusUnprocessableEntity, "commentcreate.view.tmpl.html", data)
comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
if err != nil {
app.serverError(w, r, err)
return
}
data := app.newCommonData(r)
views.GuestbookView("Guestbook", data, website, website.Guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w)
return
}
shortId := app.createShortId()
_, err = app.guestbookComments.Insert(shortId, guestbook.ID, 0, form.AuthorName, form.AuthorEmail, form.AuthorSite, form.Content, "", true)
_, 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
}
// app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!")
http.Redirect(w, r, fmt.Sprintf("/guestbooks/%s", guestbookSlug), http.StatusSeeOther)
http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook", slug), http.StatusSeeOther)
}
func (app *application) updateGuestbookComment(w http.ResponseWriter, r *http.Request) {
@ -188,8 +138,8 @@ func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Re
}
func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) {
guestbookSlug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(guestbookSlug))
slug := r.PathValue("id")
website, err := app.websites.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
@ -199,7 +149,7 @@ func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request)
return
}
comments, err := app.guestbookComments.GetQueue(guestbook.ID)
comments, err := app.guestbookComments.GetQueue(website.Guestbook.ID)
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
@ -210,7 +160,7 @@ func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request)
}
data := app.newCommonData(r)
views.GuestbookDashboardCommentsView("Message Queue", data, guestbook, comments).Render(r.Context(), w)
views.GuestbookDashboardCommentsView("Message Queue", data, website, website.Guestbook, comments).Render(r.Context(), w)
}
func (app *application) putHideGuestbookComment(w http.ResponseWriter, r *http.Request) {

View File

@ -4,128 +4,127 @@ import (
"errors"
"net/http"
"git.32bit.cafe/32bitcafe/guestbook/internal/forms"
"git.32bit.cafe/32bitcafe/guestbook/internal/forms"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
"git.32bit.cafe/32bitcafe/guestbook/internal/validator"
"git.32bit.cafe/32bitcafe/guestbook/ui/views"
)
func (app *application) getUserRegister(w http.ResponseWriter, r *http.Request) {
form := forms.UserRegistrationForm{}
data := app.newCommonData(r)
views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
form := forms.UserRegistrationForm{}
data := app.newCommonData(r)
views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
}
func (app *application) getUserLogin(w http.ResponseWriter, r *http.Request) {
views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w)
views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w)
}
func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request) {
var form forms.UserRegistrationForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.CheckField(validator.NotBlank(form.Name), "name", "This field cannot be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long")
if !form.Valid() {
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
return
}
shortId := app.createShortId()
err = app.users.Insert(shortId, form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
form.AddFieldError("email", "Email address is already in use")
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
} else {
app.serverError(w, r, err)
}
return
}
app.sessionManager.Put(r.Context(), "flash", "Registration successful. Please log in.")
http.Redirect(w, r, "/users/login", http.StatusSeeOther)
var form forms.UserRegistrationForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.CheckField(validator.NotBlank(form.Name), "name", "This field cannot be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long")
if !form.Valid() {
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
return
}
shortId := app.createShortId()
err = app.users.Insert(shortId, form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
form.AddFieldError("email", "Email address is already in use")
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
} else {
app.serverError(w, r, err)
}
return
}
app.sessionManager.Put(r.Context(), "flash", "Registration successful. Please log in.")
http.Redirect(w, r, "/users/login", http.StatusSeeOther)
}
func (app *application) postUserLogin(w http.ResponseWriter, r *http.Request) {
var form forms.UserLoginForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
}
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
if !form.Valid() {
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.UserLogin("Login", data, form).Render(r.Context(), w)
return
}
id, err := app.users.Authenticate(form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrInvalidCredentials) {
form.AddNonFieldError("Email or password is incorrect")
data := app.newCommonData(r)
views.UserLogin("Login", data, form).Render(r.Context(), w)
} else {
app.serverError(w, r, err)
}
return
}
err = app.sessionManager.RenewToken(r.Context())
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Put(r.Context(), "authenticatedUserId", id)
http.Redirect(w, r, "/", http.StatusSeeOther)
var form forms.UserLoginForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
}
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
if !form.Valid() {
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.UserLogin("Login", data, form).Render(r.Context(), w)
return
}
id, err := app.users.Authenticate(form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrInvalidCredentials) {
form.AddNonFieldError("Email or password is incorrect")
data := app.newCommonData(r)
views.UserLogin("Login", data, form).Render(r.Context(), w)
} else {
app.serverError(w, r, err)
}
return
}
err = app.sessionManager.RenewToken(r.Context())
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Put(r.Context(), "authenticatedUserId", id)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func (app *application) postUserLogout(w http.ResponseWriter, r *http.Request) {
err := app.sessionManager.RenewToken(r.Context())
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Remove(r.Context(), "authenticatedUserId")
app.sessionManager.Put(r.Context(), "flash", "You've been logged out successfully!")
http.Redirect(w, r, "/", http.StatusSeeOther)
err := app.sessionManager.RenewToken(r.Context())
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Remove(r.Context(), "authenticatedUserId")
app.sessionManager.Put(r.Context(), "flash", "You've been logged out successfully!")
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func (app *application) getUsersList(w http.ResponseWriter, r *http.Request) {
// skip templ conversion for this view, which will not be available in the final app
// something similar will be available in the admin panel
users, err := app.users.GetAll()
if err != nil {
app.serverError(w, r, err)
return
}
data := app.newTemplateData(r)
data.Users = users
app.render(w, r, http.StatusOK, "userlist.view.tmpl.html", data)
}
// func (app *application) getUsersList(w http.ResponseWriter, r *http.Request) {
// // skip templ conversion for this view, which will not be available in the final app
// // something similar will be available in the admin panel
// users, err := app.users.GetAll()
// if err != nil {
// app.serverError(w, r, err)
// return
// }
// data := app.newTemplateData(r)
// data.Users = users
// app.render(w, r, http.StatusOK, "userlist.view.tmpl.html", data)
// }
func (app *application) getUser(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("id")
user, err := app.users.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
} else {
app.serverError(w, r, err)
}
return
}
data := app.newCommonData(r)
views.UserProfile(user.Username, data, user).Render(r.Context(), w)
slug := r.PathValue("id")
user, err := app.users.Get(slugToShortId(slug))
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
http.NotFound(w, r)
} else {
app.serverError(w, r, err)
}
return
}
data := app.newCommonData(r)
views.UserProfile(user.Username, data, user).Render(r.Context(), w)
}

View File

@ -0,0 +1,92 @@
package main
import (
"errors"
"fmt"
"net/http"
"git.32bit.cafe/32bitcafe/guestbook/internal/forms"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
"git.32bit.cafe/32bitcafe/guestbook/internal/validator"
"git.32bit.cafe/32bitcafe/guestbook/ui/views"
)
func (app *application) getWebsiteCreate(w http.ResponseWriter, r *http.Request) {
form := forms.WebsiteCreateForm{}
data := app.newCommonData(r)
views.WebsiteCreate("Add Website", data, form).Render(r.Context(), w)
}
func (app *application) postWebsiteCreate(w http.ResponseWriter, r *http.Request) {
userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId")
var form forms.WebsiteCreateForm
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 exceed 256 characters")
form.CheckField(validator.NotBlank(form.Name), "sitename", "This field cannot be blank")
form.CheckField(validator.MaxChars(form.Name, 256), "sitename", "This field cannot exceed 256 characters")
form.CheckField(validator.NotBlank(form.SiteUrl), "siteurl", "This field cannot be blank")
form.CheckField(validator.MaxChars(form.SiteUrl, 512), "siteurl", "This field cannot exceed 512 characters")
if !form.Valid() {
data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity)
views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w)
}
websiteShortID := app.createShortId()
websiteId, err := app.websites.Insert(websiteShortID, userId, form.Name, form.SiteUrl, form.AuthorName)
if err != nil {
app.serverError(w, r, err)
return
}
// TODO: how to handle website creation success but guestbook creation failure?
guestbookShortID := app.createShortId()
_, err = app.guestbooks.Insert(guestbookShortID, userId, websiteId)
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Put(r.Context(), "flash", "Website successfully registered!")
if r.Header.Get("HX-Request") == "true" {
w.Header().Add("HX-Trigger", "newWebsite")
views.WebsiteCreateButton().Render(r.Context(), w)
return
}
http.Redirect(w, r, fmt.Sprintf("/websites/%s", shortIdToSlug(websiteShortID)), http.StatusSeeOther)
}
func (app *application) getWebsiteDashboard(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("id")
user := app.getCurrentUser(r)
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 user.ID != website.UserId {
app.clientError(w, http.StatusUnauthorized)
}
data := app.newCommonData(r)
views.WebsiteDashboard("Guestbook", data, website).Render(r.Context(), w)
}
func (app *application) getWebsiteList(w http.ResponseWriter, r *http.Request) {
userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId")
websites, err := app.websites.GetAll(userId)
if err != nil {
app.serverError(w, r, err)
return
}
data := app.newCommonData(r)
views.WebsiteList("My Websites", data, websites).Render(r.Context(), w)
}

View File

@ -9,133 +9,101 @@ import (
"time"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
"git.32bit.cafe/32bitcafe/guestbook/ui/views"
"github.com/gorilla/schema"
"github.com/justinas/nosurf"
)
func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) {
var (
method = r.Method
uri = r.URL.RequestURI()
)
var (
method = r.Method
uri = r.URL.RequestURI()
)
app.logger.Error(err.Error(), "method", method, "uri", uri)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
app.logger.Error(err.Error(), "method", method, "uri", uri)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
func (app *application) clientError(w http.ResponseWriter, status int) {
http.Error(w, http.StatusText(status), status)
http.Error(w, http.StatusText(status), status)
}
func (app *application) renderHTMX(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
ts, ok := app.templateCacheHTMX[page]
if !ok {
err := fmt.Errorf("the template %s does not exist", page)
app.serverError(w, r, err)
return
}
w.WriteHeader(status)
err := ts.Execute(w, data)
if err != nil {
app.serverError(w, r, err)
}
func (app *application) nextSequence() uint16 {
val := app.sequence
if app.sequence == math.MaxUint16 {
app.sequence = 0
} else {
app.sequence += 1
}
return val
}
func (app *application) renderFullPage(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
ts, ok := app.templateCache[page]
if !ok {
err := fmt.Errorf("the template %s does not exist", page)
app.serverError(w, r, err)
return
}
w.WriteHeader(status)
err := ts.ExecuteTemplate(w, "base", data)
if err != nil {
app.serverError(w, r, err)
}
}
func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
ts, ok := app.templateCache[page]
if !ok {
err := fmt.Errorf("the template %s does not exist", page)
app.serverError(w, r, err)
return
}
w.WriteHeader(status)
err := ts.ExecuteTemplate(w, "base", data)
if err != nil {
app.serverError(w, r, err)
}
}
func (app *application) nextSequence () uint16 {
val := app.sequence
if app.sequence == math.MaxUint16 {
app.sequence = 0
} else {
app.sequence += 1
}
return val
}
func (app *application) createShortId () uint64 {
now := time.Now().UTC()
epoch, err := time.Parse(time.RFC822Z, "01 Jan 20 00:00 -0000")
if err != nil {
fmt.Println(err)
return 0
}
d := now.Sub(epoch)
ms := d.Milliseconds()
seq := app.nextSequence()
return (uint64(ms) & 0x0FFFFFFFFFFFFFFF) | (uint64(seq) << 48)
func (app *application) createShortId() uint64 {
now := time.Now().UTC()
epoch, err := time.Parse(time.RFC822Z, "01 Jan 20 00:00 -0000")
if err != nil {
fmt.Println(err)
return 0
}
d := now.Sub(epoch)
ms := d.Milliseconds()
seq := app.nextSequence()
return (uint64(ms) & 0x0FFFFFFFFFFFFFFF) | (uint64(seq) << 48)
}
func shortIdToSlug(id uint64) string {
slug := strconv.FormatUint(id, 36)
return slug
slug := strconv.FormatUint(id, 36)
return slug
}
func slugToShortId(slug string) uint64 {
id, _ := strconv.ParseUint(slug, 36, 64)
return id
id, _ := strconv.ParseUint(slug, 36, 64)
return id
}
func (app *application) decodePostForm(r *http.Request, dst any) error {
err := r.ParseForm()
if err != nil {
return err
}
err := r.ParseForm()
if err != nil {
return err
}
err = app.formDecoder.Decode(dst, r.PostForm)
if err != nil {
var multiErrors *schema.MultiError
if !errors.As(err, &multiErrors) {
panic(err)
}
return err
}
return nil
err = app.formDecoder.Decode(dst, r.PostForm)
if err != nil {
var multiErrors *schema.MultiError
if !errors.As(err, &multiErrors) {
panic(err)
}
return err
}
return nil
}
func (app *application) isAuthenticated(r *http.Request) bool {
isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool)
if !ok {
return false
}
return isAuthenticated
isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool)
if !ok {
return false
}
return isAuthenticated
}
func (app *application) getCurrentUser(r *http.Request) *models.User {
if !app.isAuthenticated(r) {
return nil
}
user, ok := r.Context().Value(userNameContextKey).(models.User)
if !ok {
return nil
}
return &user
if !app.isAuthenticated(r) {
return nil
}
user, ok := r.Context().Value(userNameContextKey).(models.User)
if !ok {
return nil
}
return &user
}
func (app *application) newCommonData(r *http.Request) views.CommonData {
return views.CommonData{
CurrentYear: time.Now().Year(),
Flash: app.sessionManager.PopString(r.Context(), "flash"),
IsAuthenticated: app.isAuthenticated(r),
CSRFToken: nosurf.Token(r),
CurrentUser: app.getCurrentUser(r),
IsHtmx: r.Header.Get("Hx-Request") == "true",
}
}

View File

@ -7,7 +7,6 @@ import (
"log/slog"
"net/http"
"os"
"text/template"
"time"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
@ -18,90 +17,76 @@ import (
)
type application struct {
sequence uint16
logger *slog.Logger
templateCache map[string]*template.Template
templateCacheHTMX map[string]*template.Template
guestbooks *models.GuestbookModel
users *models.UserModel
guestbookComments *models.GuestbookCommentModel
sessionManager *scs.SessionManager
formDecoder *schema.Decoder
sequence uint16
logger *slog.Logger
websites *models.WebsiteModel
guestbooks *models.GuestbookModel
users *models.UserModel
guestbookComments *models.GuestbookCommentModel
sessionManager *scs.SessionManager
formDecoder *schema.Decoder
}
func main() {
addr := flag.String("addr", ":3000", "HTTP network address")
dsn := flag.String("dsn", "guestbook.db", "data source name")
flag.Parse()
addr := flag.String("addr", ":3000", "HTTP network address")
dsn := flag.String("dsn", "guestbook.db", "data source name")
flag.Parse()
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
db, err := openDB(*dsn)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
defer db.Close()
db, err := openDB(*dsn)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
defer db.Close()
templateCache, err := newTemplateCache()
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
sessionManager := scs.New()
sessionManager.Store = sqlite3store.New(db)
sessionManager.Lifetime = 12 * time.Hour
templateCacheHTMX, err := newHTMXTemplateCache()
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
formDecoder := schema.NewDecoder()
formDecoder.IgnoreUnknownKeys(true)
sessionManager := scs.New()
sessionManager.Store = sqlite3store.New(db)
sessionManager.Lifetime = 12 * time.Hour
app := &application{
sequence: 0,
logger: logger,
sessionManager: sessionManager,
websites: &models.WebsiteModel{DB: db},
guestbooks: &models.GuestbookModel{DB: db},
users: &models.UserModel{DB: db},
guestbookComments: &models.GuestbookCommentModel{DB: db},
formDecoder: formDecoder,
}
formDecoder := schema.NewDecoder()
formDecoder.IgnoreUnknownKeys(true)
tlsConfig := &tls.Config{
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
}
app := &application{
sequence: 0,
templateCache: templateCache,
templateCacheHTMX: templateCacheHTMX,
logger: logger,
sessionManager: sessionManager,
guestbooks: &models.GuestbookModel{DB: db},
users: &models.UserModel{DB: db},
guestbookComments: &models.GuestbookCommentModel{DB: db},
formDecoder: formDecoder,
}
srv := &http.Server{
Addr: *addr,
Handler: app.routes(),
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
TLSConfig: tlsConfig,
IdleTimeout: time.Minute,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
tlsConfig := &tls.Config{
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
}
logger.Info("Starting server", slog.Any("addr", *addr))
srv := &http.Server {
Addr: *addr,
Handler: app.routes(),
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
TLSConfig: tlsConfig,
IdleTimeout: time.Minute,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
logger.Info("Starting server", slog.Any("addr", *addr))
err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
logger.Error(err.Error())
os.Exit(1)
err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
logger.Error(err.Error())
os.Exit(1)
}
func openDB(dsn string) (*sql.DB, error) {
db, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
db, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}

View File

@ -7,35 +7,33 @@ import (
)
func (app *application) routes() http.Handler {
mux := http.NewServeMux()
fileServer := http.FileServer(http.Dir("./ui/static"))
mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))
dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate)
standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)
mux := http.NewServeMux()
fileServer := http.FileServer(http.Dir("./ui/static"))
mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))
mux.Handle("/{$}", dynamic.ThenFunc(app.home))
mux.Handle("POST /guestbooks/{id}/comments/create", standard.ThenFunc(app.postGuestbookCommentCreate))
mux.Handle("GET /guestbooks/{id}", dynamic.ThenFunc(app.getGuestbook))
mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister))
mux.Handle("POST /users/register", dynamic.ThenFunc(app.postUserRegister))
mux.Handle("GET /users/login", dynamic.ThenFunc(app.getUserLogin))
mux.Handle("POST /users/login", dynamic.ThenFunc(app.postUserLogin))
dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate)
standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)
protected := dynamic.Append(app.requireAuthentication)
mux.Handle("/{$}", dynamic.ThenFunc(app.home))
mux.Handle("POST /websites/{id}/guestbook/comments/create", standard.ThenFunc(app.postGuestbookCommentCreate))
mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook))
mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister))
mux.Handle("POST /users/register", dynamic.ThenFunc(app.postUserRegister))
mux.Handle("GET /users/login", dynamic.ThenFunc(app.getUserLogin))
mux.Handle("POST /users/login", dynamic.ThenFunc(app.postUserLogin))
mux.Handle("GET /users", protected.ThenFunc(app.getUsersList))
mux.Handle("GET /users/{id}", protected.ThenFunc(app.getUser))
mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout))
mux.Handle("GET /guestbooks", protected.ThenFunc(app.getGuestbookList))
mux.Handle("GET /guestbooks/create", protected.ThenFunc(app.getGuestbookCreate))
mux.Handle("POST /guestbooks/create", protected.ThenFunc(app.postGuestbookCreate))
mux.Handle("GET /guestbooks/{id}/dashboard", protected.ThenFunc(app.getGuestbookDashboard))
mux.Handle("GET /guestbooks/{id}/dashboard/comments", protected.ThenFunc(app.getGuestbookComments))
mux.Handle("GET /guestbooks/{id}/comments/create", protected.ThenFunc(app.getGuestbookCommentCreate))
mux.Handle("GET /guestbooks/{id}/dashboard/comments/queue", protected.ThenFunc(app.getCommentQueue))
protected := dynamic.Append(app.requireAuthentication)
// mux.Handle("GET /users", protected.ThenFunc(app.getUsersList))
mux.Handle("GET /users/{id}", protected.ThenFunc(app.getUser))
mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout))
mux.Handle("GET /websites", protected.ThenFunc(app.getWebsiteList))
mux.Handle("GET /websites/create", protected.ThenFunc(app.getWebsiteCreate))
mux.Handle("POST /websites/create", protected.ThenFunc(app.postWebsiteCreate))
mux.Handle("GET /websites/{id}/dashboard", protected.ThenFunc(app.getWebsiteDashboard))
mux.Handle("GET /websites/{id}/dashboard/guestbook/comments", protected.ThenFunc(app.getGuestbookComments))
mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/queue", protected.ThenFunc(app.getCommentQueue))
mux.Handle("GET /websites/{id}/guestbook/comments/create", protected.ThenFunc(app.getGuestbookCommentCreate))
return standard.Then(mux)
return standard.Then(mux)
}

View File

@ -1,100 +0,0 @@
package main
import (
"net/http"
"path/filepath"
"text/template"
"time"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
"git.32bit.cafe/32bitcafe/guestbook/ui/views"
"github.com/justinas/nosurf"
)
type templateData struct {
CurrentYear int
User models.User
Users []models.User
Guestbook models.Guestbook
Guestbooks []models.Guestbook
Comment models.GuestbookComment
Comments []models.GuestbookComment
Flash string
Form any
IsAuthenticated bool
CSRFToken string
CurrentUser *models.User
}
func humanDate(t time.Time) string {
return t.Format("02 Jan 2006 at 15:04")
}
var functions = template.FuncMap {
"humanDate": humanDate,
"shortIdToSlug": shortIdToSlug,
"slugToShortId": slugToShortId,
}
func newHTMXTemplateCache() (map[string]*template.Template, error) {
cache := map[string]*template.Template{}
pages, err := filepath.Glob("./ui/html/htmx/*.part.html")
if err != nil {
return nil, err
}
for _, page := range pages {
name := filepath.Base(page)
ts, err := template.New(name).Funcs(functions).ParseFiles(page)
if err != nil {
return nil, err
}
cache[name] = ts
}
return cache, nil
}
func newTemplateCache() (map[string]*template.Template, error) {
cache := map[string]*template.Template{}
pages, err := filepath.Glob("./ui/html/pages/*.tmpl.html")
if err != nil {
return nil, err
}
for _, page := range pages {
name := filepath.Base(page)
ts, err := template.New(name).Funcs(functions).ParseFiles("./ui/html/base.tmpl.html")
if err != nil {
return nil, err
}
ts, err = ts.ParseGlob("./ui/html/partials/*.tmpl.html")
if err != nil {
return nil, err
}
ts, err = ts.ParseFiles(page)
if err != nil {
return nil, err
}
cache[name] = ts
}
return cache, nil
}
func (app *application) newCommonData(r *http.Request) views.CommonData {
return views.CommonData {
CurrentYear: time.Now().Year(),
Flash: app.sessionManager.PopString(r.Context(), "flash"),
IsAuthenticated: app.isAuthenticated(r),
CSRFToken: nosurf.Token(r),
CurrentUser: app.getCurrentUser(r),
IsHtmx: r.Header.Get("Hx-Request") == "true",
}
}
func (app *application) newTemplateData(r *http.Request) templateData {
return templateData {
CurrentYear: time.Now().Year(),
Flash: app.sessionManager.PopString(r.Context(), "flash"),
IsAuthenticated: app.isAuthenticated(r),
CSRFToken: nosurf.Token(r),
CurrentUser: app.getCurrentUser(r),
}
}

View File

@ -1,5 +0,0 @@
CREATE TABLE sessions (
token CHAR(43) primary key,
data BLOB NOT NULL,
expiry TEXT NOT NULL
);

View File

@ -9,10 +9,24 @@ CREATE TABLE users (
Created datetime NOT NULL
);
CREATE TABLE websites (
Id integer primary key autoincrement,
ShortId integer UNIQUE NOT NULL,
Name varchar(256) NOT NULL,
SiteUrl varchar(512) NOT NULL,
AuthorName varchar(512) NOT NULL,
UserId integer NOT NULL,
Created datetime NOT NULL,
Deleted datetime,
FOREIGN KEY (UserId) REFERENCES users(Id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE TABLE guestbooks (
Id integer primary key autoincrement,
ShortId integer UNIQUE NOT NULL,
SiteUrl varchar(512) NOT NULL,
WebsiteId integer UNIQUE NOT NULL,
UserId integer NOT NULL,
Created datetime NOT NULL,
IsDeleted boolean NOT NULL DEFAULT FALSE,
@ -20,6 +34,9 @@ CREATE TABLE guestbooks (
FOREIGN KEY (UserId) REFERENCES users(Id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
FOREIGN KEY (WebsiteId) REFERENCES websites(Id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE TABLE guestbook_comments (
@ -44,3 +61,9 @@ CREATE TABLE guestbook_comments (
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE TABLE sessions (
token CHAR(43) primary key,
data BLOB NOT NULL,
expiry TEXT NOT NULL
);

View File

@ -3,23 +3,29 @@ package forms
import "git.32bit.cafe/32bitcafe/guestbook/internal/validator"
type UserRegistrationForm struct {
Name string `schema:"username"`
Email string `schema:"email"`
Password string `schema:"password"`
validator.Validator `schema:"-"`
Name string `schema:"username"`
Email string `schema:"email"`
Password string `schema:"password"`
validator.Validator `schema:"-"`
}
type UserLoginForm struct {
Email string `schema:"email"`
Password string `schema:"password"`
validator.Validator `schema:"-"`
Email string `schema:"email"`
Password string `schema:"password"`
validator.Validator `schema:"-"`
}
type CommentCreateForm struct {
AuthorName string `schema:"authorname"`
AuthorEmail string `schema:"authoremail"`
AuthorSite string `schema:"authorsite"`
Content string `schema:"content,required"`
validator.Validator `schema:"-"`
AuthorName string `schema:"authorname"`
AuthorEmail string `schema:"authoremail"`
AuthorSite string `schema:"authorsite"`
Content string `schema:"content,required"`
validator.Validator `schema:"-"`
}
type WebsiteCreateForm struct {
Name string `schema:"sitename"`
SiteUrl string `schema:"siteurl"`
AuthorName string `schema:"authorname"`
validator.Validator `schema:"-"`
}

View File

@ -8,8 +8,8 @@ import (
type Guestbook struct {
ID int64
ShortId uint64
SiteUrl string
UserId int64
WebsiteId int64
Created time.Time
IsDeleted bool
IsActive bool
@ -19,10 +19,10 @@ type GuestbookModel struct {
DB *sql.DB
}
func (m *GuestbookModel) Insert(shortId uint64, siteUrl string, userId int64) (int64, error) {
stmt := `INSERT INTO guestbooks (ShortId, SiteUrl, UserId, Created, IsDeleted, IsActive)
func (m *GuestbookModel) Insert(shortId uint64, userId int64, websiteId int64) (int64, error) {
stmt := `INSERT INTO guestbooks (ShortId, UserId, WebsiteId, Created, IsDeleted, IsActive)
VALUES(?, ?, ?, ?, FALSE, TRUE)`
result, err := m.DB.Exec(stmt, shortId, siteUrl, userId, time.Now().UTC())
result, err := m.DB.Exec(stmt, shortId, userId, websiteId, time.Now().UTC())
if err != nil {
return -1, err
}
@ -34,11 +34,11 @@ func (m *GuestbookModel) Insert(shortId uint64, siteUrl string, userId int64) (i
}
func (m *GuestbookModel) Get(shortId uint64) (Guestbook, error) {
stmt := `SELECT Id, ShortId, SiteUrl, UserId, Created, IsDeleted, IsActive FROM guestbooks
stmt := `SELECT Id, ShortId, UserId, WebsiteId, Created, IsDeleted, IsActive FROM guestbooks
WHERE ShortId = ?`
row := m.DB.QueryRow(stmt, shortId)
var g Guestbook
err := row.Scan(&g.ID, &g.ShortId, &g.SiteUrl, &g.UserId, &g.Created, &g.IsDeleted, &g.IsActive)
err := row.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &g.IsDeleted, &g.IsActive)
if err != nil {
return Guestbook{}, err
}
@ -47,7 +47,7 @@ func (m *GuestbookModel) Get(shortId uint64) (Guestbook, error) {
}
func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) {
stmt := `SELECT Id, ShortId, SiteUrl, UserId, Created, IsDeleted, IsActive FROM guestbooks
stmt := `SELECT Id, ShortId, UserId, WebsiteId, Created, IsDeleted, IsActive FROM guestbooks
WHERE UserId = ?`
rows, err := m.DB.Query(stmt, userId)
if err != nil {
@ -56,7 +56,7 @@ func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) {
var guestbooks []Guestbook
for rows.Next() {
var g Guestbook
err = rows.Scan(&g.ID, &g.ShortId, &g.SiteUrl, &g.UserId, &g.Created, &g.IsDeleted, &g.IsActive)
err = rows.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &g.IsDeleted, &g.IsActive)
if err != nil {
return nil, err
}

View File

@ -6,110 +6,110 @@ import (
)
type GuestbookComment struct {
ID int64
ShortId uint64
GuestbookId int64
ParentId int64
AuthorName string
AuthorEmail string
AuthorSite string
CommentText string
PageUrl string
Created time.Time
IsPublished bool
IsDeleted bool
ID int64
ShortId uint64
GuestbookId int64
ParentId int64
AuthorName string
AuthorEmail string
AuthorSite string
CommentText string
PageUrl string
Created time.Time
IsPublished bool
IsDeleted bool
}
type GuestbookCommentModel struct {
DB *sql.DB
DB *sql.DB
}
func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName,
authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) {
stmt := `INSERT INTO guestbook_comments (ShortId, GuestbookId, ParentId, AuthorName,
authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) {
stmt := `INSERT INTO guestbook_comments (ShortId, GuestbookId, ParentId, AuthorName,
AuthorEmail, AuthorSite, CommentText, PageUrl, Created, IsPublished, IsDeleted)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE)`
result, err := m.DB.Exec(stmt, shortId, guestbookId, parentId, authorName, authorEmail,
authorSite, commentText, pageUrl, time.Now().UTC(), isPublished)
if err != nil {
return -1, err
}
id, err := result.LastInsertId()
if err != nil {
return -1, err
}
return id, nil
result, err := m.DB.Exec(stmt, shortId, guestbookId, parentId, authorName, authorEmail,
authorSite, commentText, pageUrl, time.Now().UTC(), isPublished)
if err != nil {
return -1, err
}
id, err := result.LastInsertId()
if err != nil {
return -1, err
}
return id, nil
}
func (m *GuestbookCommentModel) Get(shortId uint64) (GuestbookComment, error) {
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
CommentText, PageUrl, Created, IsPublished, IsDeleted FROM guestbook_comments WHERE ShortId = ?`
row := m.DB.QueryRow(stmt, shortId)
var c GuestbookComment
err := row.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted)
if err != nil {
return GuestbookComment{}, err
}
return c, nil
row := m.DB.QueryRow(stmt, shortId)
var c GuestbookComment
err := row.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted)
if err != nil {
return GuestbookComment{}, err
}
return c, nil
}
func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, error) {
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
CommentText, PageUrl, Created, IsPublished, IsDeleted
FROM guestbook_comments
WHERE GuestbookId = ? AND IsDeleted = FALSE AND IsPublished = TRUE
ORDER BY Created DESC`
rows, err := m.DB.Query(stmt, guestbookId)
if err != nil {
return nil, err
}
var comments []GuestbookComment
for rows.Next() {
var c GuestbookComment
err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted)
rows, err := m.DB.Query(stmt, guestbookId)
if err != nil {
return nil, err
return nil, err
}
comments = append(comments, c)
}
if err = rows.Err(); err != nil {
return nil, err
}
return comments, nil
var comments []GuestbookComment
for rows.Next() {
var c GuestbookComment
err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted)
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) GetQueue(guestbookId int64) ([]GuestbookComment, error) {
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
CommentText, PageUrl, Created, IsPublished, IsDeleted
FROM guestbook_comments
WHERE GuestbookId = ? AND IsDeleted = FALSE AND IsPublished = FALSE
ORDER BY Created DESC`
rows, err := m.DB.Query(stmt, guestbookId)
if err != nil {
return nil, err
}
var comments []GuestbookComment
for rows.Next() {
var c GuestbookComment
err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted)
rows, err := m.DB.Query(stmt, guestbookId)
if err != nil {
return nil, err
return nil, err
}
comments = append(comments, c)
}
if err = rows.Err(); err != nil {
return nil, err
}
return comments, nil
var comments []GuestbookComment
for rows.Next() {
var c GuestbookComment
err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted)
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) UpdateComment(comment *GuestbookComment) error {
stmt := `UPDATE guestbook_comments (CommentText, PageUrl, IsPublished, IsDeleted)
stmt := `UPDATE guestbook_comments (CommentText, PageUrl, IsPublished, IsDeleted)
VALUES (?, ?, ?, ?)
WHERE Id = ?`
_, err := m.DB.Exec(stmt, comment.CommentText, comment.PageUrl, comment.IsPublished, comment.IsDeleted, comment.ID)
if err != nil {
return err
}
return nil
_, err := m.DB.Exec(stmt, comment.CommentText, comment.PageUrl, comment.IsPublished, comment.IsDeleted, comment.ID)
if err != nil {
return err
}
return nil
}

102
internal/models/website.go Normal file
View File

@ -0,0 +1,102 @@
package models
import (
"database/sql"
"time"
)
type Website struct {
ID int64
ShortId uint64
Name string
SiteUrl string
AuthorName string
UserId int64
Created time.Time
Deleted time.Time
Guestbook Guestbook
}
type WebsiteModel struct {
DB *sql.DB
}
func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) {
stmt := `INSERT INTO websites (ShortId, Name, SiteUrl, AuthorName, UserId, Created)
VALUES (?, ?, ?, ?, ?, ?)`
result, err := m.DB.Exec(stmt, shortId, siteName, siteUrl, authorName, userId, time.Now().UTC())
if err != nil {
return -1, err
}
id, err := result.LastInsertId()
if err != nil {
return -1, err
}
return id, nil
}
func (m *WebsiteModel) Get(shortId uint64) (Website, error) {
stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, w.Deleted,
g.Id, g.ShortId, g.Created, g.IsDeleted, g.IsActive
FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId
WHERE w.ShortId = ?`
row := m.DB.QueryRow(stmt, shortId)
var t sql.NullTime
var w Website
err := row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, &t,
&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsDeleted, &w.Guestbook.IsActive)
if err != nil {
return Website{}, err
}
// handle if Deleted is null
if t.Valid {
w.Deleted = t.Time
}
return w, nil
}
func (m *WebsiteModel) GetById(id int64) (Website, error) {
stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, w.Deleted,
g.Id, g.ShortId, g.Created, g.IsDeleted, g.IsActive
FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId
WHERE w.Id = ?`
row := m.DB.QueryRow(stmt, id)
var t sql.NullTime
var w Website
err := row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, &t,
&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsDeleted, &w.Guestbook.IsActive)
if err != nil {
return Website{}, err
}
// handle if Deleted is null
if t.Valid {
w.Deleted = t.Time
}
return w, nil
}
func (m *WebsiteModel) GetAll(userId int64) ([]Website, error) {
stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, w.Deleted,
g.Id, g.ShortId, g.Created, g.IsDeleted, g.IsActive
FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId
WHERE w.UserId = ?`
rows, err := m.DB.Query(stmt, userId)
if err != nil {
return nil, err
}
var websites []Website
for rows.Next() {
var t sql.NullTime
var w Website
err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, &t,
&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsDeleted, &w.Guestbook.IsActive)
if err != nil {
return nil, err
}
websites = append(websites, w)
}
if err = rows.Err(); err != nil {
return nil, err
}
return websites, nil
}

View File

@ -1,27 +0,0 @@
{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ template "title" }} - webweav.ing</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/css/style.css" rel="stylesheet">
<script src="/static/js/htmx.min.js"></script>
</head>
<body>
<header>
<h1><a href="/">webweav.ing</a></h1>
</header>
{{ template "nav" . }}
<main>
{{ with .Flash }}
<div class="flash">{{ . }}</div>
{{ end }}
{{ template "main" . }}
</main>
<footer>
<p>A 32bit.cafe Project</p>
</footer>
</body>
</html>
{{ end }}

View File

@ -1,6 +0,0 @@
<form hx-post="/guestbooks/create" hx-target="closest div">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<label for="siteurl">Site URL: </label>
<input type="text" name="siteurl" id="siteurl" required />
<button type="submit">Submit</button>
</form>

View File

@ -1 +0,0 @@
<button hx-get="/guestbooks/create" hx-target="closest">New Guestbook</button>

View File

@ -1,7 +0,0 @@
<ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body">
{{ range .Guestbooks }}
<li><a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a></li>
{{ else }}
<p>No Guestbooks yet</p>
{{ end }}
</ul>

View File

@ -1,5 +0,0 @@
{{ with .Guestbook }}
<li>
<a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a>
</li>
{{ end }}

View File

@ -1,37 +0,0 @@
{{ define "title" }}New Comment{{ end }}
{{ define "main" }}
<form action="/guestbooks/{{ shortIdToSlug .Guestbook.ShortId }}/comments/create" method="post">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<div>
<label for="authorname">Name: </label>
{{ with .Form.FieldErrors.authorName }}
<label class="error">{{.}}</label>
{{ end }}
<input type="text" name="authorname" id="authorname" >
</div>
<div>
<label for="authoremail">Email: </label>
{{ with .Form.FieldErrors.authorEmail }}
<label class="error">{{.}}</label>
{{ end }}
<input type="text" name="authoremail" id="authoremail" >
</div>
<div>
<label for="authorsite">Site Url: </label>
{{ with .Form.FieldErrors.authorSite }}
<label class="error">{{.}}</label>
{{ end }}
<input type="text" name="authorsite" id="authorsite" >
</div>
<div>
<label for="content">Comment: </label>
{{ with .Form.FieldErrors.content }}
<label class="error">{{.}}</label>
{{ end }}
<textarea name="content" id="content"></textarea>
</div>
<div>
<input type="submit" value="Post">
</div>
</form>
{{ end }}

View File

@ -1,18 +0,0 @@
{{ define "title" }} Guestbook View {{ end }}
{{ define "main" }}
<h1>Guestbook for {{ .Guestbook.SiteUrl }}</h1>
<p>
<a href="/guestbooks/{{ shortIdToSlug .Guestbook.ShortId }}/comments/create">New Comment</a>
</p>
{{ range .Comments }}
<div>
<strong> {{ .AuthorName }} </strong>
{{ .Created.Local.Format "01-02-2006 03:04PM" }}
<p>
{{ .CommentText }}
</p>
</div>
{{ else }}
<p>No comments yet!</p>
{{ end }}
{{ end }}

View File

@ -1,4 +0,0 @@
{{ define "title" }}Create a Guestbook{{ end }}
{{ define "main" }}
{{ template "guestbookcreate" }}
{{ end }}

View File

@ -1,14 +0,0 @@
{{ define "title" }} Guestbooks {{ end }}
{{ define "main" }}
<h1>Guestbooks run by {{ .User.Username }}</h1>
<div>
<button hx-get="/guestbooks/create" hx-target="closest div">New Guestbook</button>
</div>
<ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body" hx-swap="outerHTML">
{{ range .Guestbooks }}
<li><a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a></li>
{{ else }}
<p>No Guestbooks yet</p>
{{ end }}
</ul>
{{ end }}

View File

@ -1,12 +0,0 @@
{{ define "title" }}Home{{ end }}
{{ define "main" }}
{{ if .IsAuthenticated }}
<h2>Tools</h2>
<p>
<a href="/guestbooks">Guestbooks</a>
</p>
{{ else }}
<h2>Welcome</h2>
Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>.
{{ end }}
{{ end }}

View File

@ -1,26 +0,0 @@
{{define "title"}}Login{{end}}
{{define "main"}}
<form action="/users/login" method="POST" novalidate>
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
{{ range .Form.NonFieldErrors }}
<div class="error">{{.}}</div>
{{ end }}
<div>
<label>Email: </label>
{{ with .Form.FieldErrors.email }}
<label class="error">{{.}}</label>
{{ end }}
<input type="email" name="email" value="{{.Form.Email}}">
</div>
<div>
<label>Password: </label>
{{ with .Form.FieldErrors.password }}
<label class="error">{{.}}</label>
{{ end }}
<input type="password" name="password">
</div>
<div>
<input type="submit" value="login">
</div>
</form>
{{end}}

View File

@ -1,5 +0,0 @@
{{ define "title" }}{{ .User.Username }}{{ end }}
{{ define "main" }}
<h1>{{ .User.Username }}</h1>
<p>{{ .User.Email }}</p>
{{ end }}

View File

@ -1,30 +0,0 @@
{{ define "title" }}User Registration{{ end }}
{{ define "main" }}
<form action="/users/register" method="post">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<div>
<label for="username">Username: </label>
{{ with .Form.FieldErrors.name }}
<label class="error">{{.}}</label>
{{ end }}
<input type="text" name="username" id="username" value="{{ .Form.Name }}" required />
</div>
<div>
<label for="email">Email: </label>
{{ with .Form.FieldErrors.email }}
<label class="error">{{.}}</label>
{{ end }}
<input type="text" name="email" id="email" value="{{ .Form.Email }}" required />
</div>
<div>
<label for="password">Password: </label>
{{ with .Form.FieldErrors.password }}
<label class="error">{{.}}</label>
{{ end }}
<input type="password" name="password" id="password" />
</div>
<div>
<input type="submit" value="Register"/>
</div>
</form>
{{ end }}

View File

@ -1,9 +0,0 @@
{{ define "title" }}Users{{ end }}
{{ define "main" }}
<h1>Users</h1>
{{ range .Users }}
<p>
<a href="/users/{{ shortIdToSlug .ShortId }}">{{ .Username }}</a>
</p>
{{ end }}
{{ end }}

View File

@ -1,8 +0,0 @@
{{ define "guestbookcreate" }}
<form action="/guestbooks/create" method="post">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<label for="siteurl">Site URL: </label>
<input type="text" name="siteurl" id="siteurl" required />
<input type="submit" />
</form>
{{ end }}

View File

@ -1,22 +0,0 @@
{{ define "nav" }}
<nav>
<div>
<a href="/">Home</a>
<a href="/users">Users</a>
</div>
<div>
{{ if .IsAuthenticated }}
{{ with .CurrentUser }}
Welcome, {{ .Username }}
{{ end }}
<form action="/users/logout" method="post">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<button>Logout</button>
</form>
{{ else }}
<a href="/users/register">Register</a>
<a href="/users/login">Login</a>
{{ end }}
</div>
</nav>
{{ end }}

View File

@ -30,14 +30,13 @@ templ commonHeader() {
templ topNav(data CommonData) {
<nav>
<div>
<ul>
<li><a href="/guestbooks">Guestbooks</a></li>
<li>RSS Feeds</li>
</ul>
if data.IsAuthenticated {
Welcome, { data.CurrentUser.Username }
}
</div>
<div>
if data.IsAuthenticated {
Welcome, { data.CurrentUser.Username }
<a href="/websites">My Websites</a> |
<a href="/users/settings">Settings</a> |
<form action="/users/logout" method="post">
<input type="hidden" name="csrf_token" value={ data.CSRFToken }>

View File

@ -79,7 +79,7 @@ func topNav(data CommonData) templ.Component {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div><ul><li><a href=\"/guestbooks\">Guestbooks</a></li><li>RSS Feeds</li></ul></div><div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -91,36 +91,42 @@ func topNav(data CommonData) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 40, Col: 52}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 34, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.IsAuthenticated {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"/websites\">My Websites</a> | <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 43, Col: 81}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 42, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"> <button>Logout</button></form>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"> <button>Logout</button></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<a href=\"/users/register\">Create an Account</a> | <a href=\"/users/login\">Login</a>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> | <a href=\"/users/login\">Login</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div></nav>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -149,7 +155,7 @@ func commonFooter() templ.Component {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<footer><p>Generated with Templ</p><p>A <a href=\"https://32bit.cafe\">32-bit cafe</a> Project</p></footer>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<footer><p>Generated with Templ</p><p>A <a href=\"https://32bit.cafe\">32-bit cafe</a> Project</p></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -178,20 +184,20 @@ func base(title string, data CommonData) templ.Component {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<!doctype html><html lang=\"en\"><head><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<!doctype html><html lang=\"en\"><head><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 66, Col: 26}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 65, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -203,25 +209,25 @@ func base(title string, data CommonData) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<main>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Flash != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"flash\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"flash\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 77, Col: 51}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -230,7 +236,7 @@ func base(title string, data CommonData) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</main>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -238,7 +244,7 @@ func base(title string, data CommonData) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -1,7 +1,8 @@
<header><h1><a href=\"/\">webweav.ing</a></h1></header>
<nav><div><ul><li><a href=\"/guestbooks\">Guestbooks</a></li><li>RSS Feeds</li></ul></div><div>
<nav><div>
Welcome,
<a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"
</div><div>
<a href=\"/websites\">My Websites</a> | <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"
\"> <button>Logout</button></form>
<a href=\"/users/register\">Create an Account</a> | <a href=\"/users/login\">Login</a>
</div></nav>

View File

@ -22,11 +22,6 @@ templ gbList(guestbooks []models.Guestbook) {
<ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body" hx-swap="outerHTML">
for _, gb := range guestbooks {
<li><a href={ templ.URL(gbUrl(gb) + "/dashboard") }>
if len(gb.SiteUrl) == 0 {
Untitled
} else {
{ gb.SiteUrl }
}
</a></li>
}
@ -62,56 +57,12 @@ templ GuestbookCreate(title string, data CommonData) {
}
}
templ sidebar(guestbook models.Guestbook) {
{{ dashUrl := gbUrl(guestbook) + "/dashboard" }}
<nav>
<div>
<a href={ templ.URL(gbUrl(guestbook)) } target="_blank">View Guestbook</a>
<h3>Messages</h3>
<ul>
<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
<li><a href={ templ.URL(dashUrl + "/comments") }>Manage messages</a></li>
<li><a href={ templ.URL(dashUrl + "/comments/queue") }>Review message queue</a></li>
<li><a href={ templ.URL(dashUrl + "/blocklist") }>Block users</a></li>
<li><a href={ templ.URL(dashUrl + "/comments/trash") }>Trash</a></li>
</ul>
</div>
<div>
<h3>Design</h3>
<ul>
<li><a href={ templ.URL(dashUrl + "/themes") }>Themes</a></li>
<li><a href={ templ.URL(dashUrl + "/customize") }>Custom CSS</a></li>
</ul>
</div>
<div>
<h3>Account</h3>
<ul>
<li><a href="/users/settings">Settings</a></li>
<li><a href="/users/privacy">Privacy</a></li>
<li><a href="/help">Help</a></li>
</ul>
</div>
</nav>
}
templ GuestbookDashboardView(title string, data CommonData, guestbook models.Guestbook, comments []models.GuestbookComment) {
templ GuestbookDashboardCommentsView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment) {
@base(title, data) {
<div id="dashboard">
@sidebar(guestbook)
@wSidebar(website)
<div>
<h1>Guestbook for { guestbook.SiteUrl }</h1>
<p>Stats and stuff will go here</p>
</div>
</div>
}
}
templ GuestbookDashboardCommentsView(title string, data CommonData, guestbook models.Guestbook, comments []models.GuestbookComment) {
@base(title, data) {
<div id="dashboard">
@sidebar(guestbook)
<div>
<h1>Comments on { guestbook.SiteUrl }</h1>
<h1>Comments on { website.SiteUrl }</h1>
if len(comments) == 0 {
<p>No comments yet!</p>
}
@ -129,65 +80,90 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, guestbook mo
}
}
templ commentForm(data CommonData, gb models.Guestbook, form forms.CommentCreateForm) {
{{ postUrl := fmt.Sprintf("/guestbooks/%s/comments/create", shortIdToSlug(gb.ShortId)) }}
<form action={ templ.URL(postUrl) } method="post">
<input type="hidden" name="csrf_token" value={data.CSRFToken}>
<div>
<label for="authorname">Name: </label>
{{ error, exists := form.FieldErrors["authorName"] }}
if exists {
<label class="error">{ error }</label>
}
<input type="text" name="authorname" id="authorname" >
</div>
<div>
<label for="authoremail">Email: </label>
{{ error, exists = form.FieldErrors["authorEmail"] }}
if exists {
<label class="error">{ error }</label>
}
<input type="text" name="authoremail" id="authoremail" >
</div>
<div>
<label for="authorsite">Site Url: </label>
{{ error, exists = form.FieldErrors["authorSite"] }}
if exists {
<label class="error">{ error }</label>
}
<input type="text" name="authorsite" id="authorsite" >
</div>
<div>
<label for="content">Comment: </label>
{{ error, exists = form.FieldErrors["content"] }}
if exists {
<label class="error">{ error }</label>
}
<textarea name="content" id="content"></textarea>
</div>
<div>
<input type="submit" value="Submit">
</div>
</form>
}
templ commentForm(form forms.CommentCreateForm) {
<div>
<label for="authorname">Name: </label>
{{ error, exists := form.FieldErrors["authorName"] }}
if exists {
<label class="error">{ error }</label>
}
<input type="text" name="authorname" id="authorname">
</div>
<div>
<label for="authoremail">Email: </label>
{{ error, exists = form.FieldErrors["authorEmail"] }}
if exists {
<label class="error">{ error }</label>
}
<input type="text" name="authoremail" id="authoremail">
</div>
<div>
<label for="authorsite">Site Url: </label>
{{ error, exists = form.FieldErrors["authorSite"] }}
if exists {
<label class="error">{ error }</label>
}
<input type="text" name="authorsite" id="authorsite">
</div>
<div>
<label for="content">Comment: </label>
{{ error, exists = form.FieldErrors["content"] }}
if exists {
<label class="error">{ error }</label>
}
<textarea name="content" id="content"></textarea>
</div>
<div>
<input type="submit" value="Submit">
</div>
}
templ GuestbookView(title string, data CommonData, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) {
templ GuestbookCommentList(comments []models.GuestbookComment) {
if len(comments) == 0 {
<p>No comments yet!</p>
}
for _, c := range comments {
<div>
<strong>{ c.AuthorName }</strong>
{ c.Created.Format("01-02-2006 03:04PM") }
<p>
{ c.CommentText }
</p>
</div>
}
}
templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) {
{{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }}
if data.IsHtmx {
@commentForm(form)
} else {
<div id="main">
<div>
<h1>Guestbook for { guestbook.SiteUrl }</h1>
@commentForm(data, guestbook, form)
if len(comments) == 0 {
<p>No comments yet!</p>
}
for _, c := range comments {
<div>
<strong>{ c.AuthorName }</strong>
{ c.Created.Format("01-02-2006 03:04PM") }
<p>
{ c.CommentText }
</p>
</div>
}
<h1>Guestbook for { website.SiteUrl }</h1>
<form action={ templ.URL(postUrl) } method="post">
<input type="hidden" name="csrf_token" value={data.CSRFToken}>
@commentForm(form)
</form>
</div>
<div id="comments">
@GuestbookCommentList(comments)
</div>
</div>
}
}
templ CreateGuestbookComment(title string, data CommonData, website models.Website, guestbook models.Guestbook, form forms.CommentCreateForm) {
{{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }}
if data.IsHtmx {
<form hx-post={ postUrl } hx-target="closest div">
@commentForm(form)
</form>
} else {
@base(title, data) {
<form action={ templ.URL(postUrl) } method="post">
@commentForm(form)
</form>
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,27 +3,13 @@
<p>No Guestbooks yet</p>
<ul id=\"guestbooks\" hx-get=\"/guestbooks\" hx-trigger=\"newGuestbook from:body\" hx-swap=\"outerHTML\">
<li><a href=\"
\">
Untitled
</a></li>
\"></a></li>
</ul>
<h1>My Guestbooks</h1><div><button hx-get=\"/guestbooks/create\" hx-target=\"closest div\">New Guestbook</button></div>
<form hx-post=\"/guestbooks/create\" hx-target=\"closest div\">
</form>
<form action=\"/guestbooks/create\" method=\"post\">
</form>
<nav><div><a href=\"
\" target=\"_blank\">View Guestbook</a><h3>Messages</h3><ul><li><a href=\"
\">Dashboard</a></li><li><a href=\"
\">Manage messages</a></li><li><a href=\"
\">Review message queue</a></li><li><a href=\"
\">Block users</a></li><li><a href=\"
\">Trash</a></li></ul></div><div><h3>Design</h3><ul><li><a href=\"
\">Themes</a></li><li><a href=\"
\">Custom CSS</a></li></ul></div><div><h3>Account</h3><ul><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"/users/privacy\">Privacy</a></li><li><a href=\"/help\">Help</a></li></ul></div></nav>
<div id=\"dashboard\">
<div><h1>Guestbook for
</h1><p>Stats and stuff will go here</p></div></div>
<div id=\"dashboard\">
<div><h1>Comments on
</h1>
@ -33,9 +19,7 @@ Untitled
<p>
</p></div>
</div></div>
<form action=\"
\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"
\"><div><label for=\"authorname\">Name: </label>
<div><label for=\"authorname\">Name: </label>
<label class=\"error\">
</label>
<input type=\"text\" name=\"authorname\" id=\"authorname\"></div><div><label for=\"authoremail\">Email: </label>
@ -47,12 +31,21 @@ Untitled
<input type=\"text\" name=\"authorsite\" id=\"authorsite\"></div><div><label for=\"content\">Comment: </label>
<label class=\"error\">
</label>
<textarea name=\"content\" id=\"content\"></textarea></div><div><input type=\"submit\" value=\"Submit\"></div></form>
<div id=\"main\"><div><h1>Guestbook for
</h1>
<textarea name=\"content\" id=\"content\"></textarea></div><div><input type=\"submit\" value=\"Submit\"></div>
<p>No comments yet!</p>
<div><strong>
</strong>
<p>
</p></div>
</div></div>
<div id=\"main\"><div><h1>Guestbook for
</h1><form action=\"
\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"
\">
</form></div><div id=\"comments\">
</div></div>
<form hx-post=\"
\" hx-target=\"closest div\">
</form>
<form action=\"
\" method=\"post\">
</form>

View File

@ -1,25 +1,10 @@
package views
templ loggedInHome() {
<h2>Tools</h2>
<p>
<a href="/guestbooks">Guestbooks</a>
</p>
}
templ loggedOutHome() {
<h2>Welcome</h2>
<p>
Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>.
</p>
}
templ Home(title string, data CommonData) {
@base(title, data) {
if data.IsAuthenticated {
@loggedInHome()
} else {
@loggedOutHome()
}
<h2>Welcome</h2>
<p>
Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>.
</p>
}
}

View File

@ -8,7 +8,7 @@ package views
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func loggedInHome() templ.Component {
func Home(title string, data CommonData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -29,65 +29,7 @@ func loggedInHome() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Tools</h2><p><a href=\"/guestbooks\">Guestbooks</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func loggedOutHome() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func Home(title string, data CommonData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@ -99,20 +41,13 @@ func Home(title string, data CommonData) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
if data.IsAuthenticated {
templ_7745c5c3_Err = loggedInHome().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = loggedOutHome().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -1,2 +1 @@
<h2>Tools</h2><p><a href=\"/guestbooks\">Guestbooks</a></p>
<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p>

139
ui/views/websites.templ Normal file
View File

@ -0,0 +1,139 @@
package views
import "fmt"
import "git.32bit.cafe/32bitcafe/guestbook/internal/models"
import "git.32bit.cafe/32bitcafe/guestbook/internal/forms"
func wUrl (w models.Website) string {
return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId))
}
templ wSidebar(website models.Website) {
{{ dashUrl := wUrl(website) + "/dashboard" }}
{{ gbUrl := wUrl(website) + "/guestbook" }}
<nav>
<div>
<ul>
<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
<li><a href={ templ.URL(website.SiteUrl) }>View Website</a></li>
</ul>
<h3>Guestbook</h3>
<ul>
<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li>
</ul>
<ul>
<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li>
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li>
<li><a href={ templ.URL(dashUrl + "/guestbook/blocklist") }>Block users</a></li>
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li>
</ul>
<ul>
<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li>
<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li>
</ul>
</div>
<div>
<h3>Feeds</h3>
<p>Coming Soon</p>
</div>
<div>
<h3>Account</h3>
<ul>
<li><a href="/users/settings">Settings</a></li>
<li><a href="/users/privacy">Privacy</a></li>
<li><a href="/help">Help</a></li>
</ul>
</div>
</nav>
}
templ displayWebsites (websites []models.Website) {
if len(websites) == 0 {
<p>No Websites yet. <a href="">Register a website.</a></p>
} else {
<ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML">
for _, w := range websites {
<li>
<a href={ templ.URL(wUrl(w) + "/dashboard")}>{ w.Name }</a>
</li>
}
</ul>
}
}
templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
<input type="hidden" name="csrf_token" value={csrfToken}>
<div>
{{ err, exists := form.FieldErrors["sitename"]}}
<label for="sitename">Site Name: </label>
if exists {
<label class="error">{ err }</label>
}
<input type="text" name="sitename" id="sitename" required />
</div>
<div>
{{ err, exists = form.FieldErrors["siteurl"] }}
<label for="siteurl">Site URL: </label>
if exists {
<label class="error">{ err }</label>
}
<input type="text" name="siteurl" id="siteurl" required />
</div>
<div>
{{ err, exists = form.FieldErrors["authorname"] }}
<label for="authorname">Site Author: </label>
if exists {
<label class="error">{ err }</label>
}
<input type="text" name="authorname" id="authorname" required />
</div>
<div>
<button type="submit">Submit</button>
</div>
}
templ WebsiteCreateButton() {
<button hx-get="/websites/create" hx-target="closest div">Add Website</button>
}
templ WebsiteList(title string, data CommonData, websites []models.Website) {
if data.IsHtmx {
@displayWebsites(websites)
} else {
@base(title, data) {
<h1>My Websites</h1>
<div>
@WebsiteCreateButton()
</div>
<div>
@displayWebsites(websites)
</div>
}
}
}
templ WebsiteDashboard(title string, data CommonData, website models.Website) {
@base(title, data) {
<div id="dashboard">
@wSidebar(website)
<div>
<h1>{ website.Name }</h1>
<p>
Stats and stuff will go here.
</p>
</div>
</div>
}
}
templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) {
if data.IsHtmx {
<form hx-post="/websites/create" hx-target="closest div">
@websiteCreateForm(data.CSRFToken, form)
</form>
} else {
<form action="/websites/create" method="post">
@websiteCreateForm(data.CSRFToken, form)
</form>
}
}

544
ui/views/websites_templ.go Normal file
View File

@ -0,0 +1,544 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "fmt"
import "git.32bit.cafe/32bitcafe/guestbook/internal/models"
import "git.32bit.cafe/32bitcafe/guestbook/internal/forms"
func wUrl(w models.Website) string {
return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId))
}
func wSidebar(website models.Website) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
dashUrl := wUrl(website) + "/dashboard"
gbUrl := wUrl(website) + "/guestbook"
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav><div><ul><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(dashUrl)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">Dashboard</a></li><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(website.SiteUrl)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">View Website</a></li></ul><h3>Guestbook</h3><ul><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(gbUrl)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" target=\"_blank\">View Guestbook</a></li></ul><ul><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(dashUrl + "/guestbook/comments")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">Manage messages</a></li><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(dashUrl + "/guestbook/comments/queue")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Review message queue</a></li><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(dashUrl + "/guestbook/blocklist")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\">Block users</a></li><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.SafeURL = templ.URL(dashUrl + "/guestbook/comments/trash")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Trash</a></li></ul><ul><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 templ.SafeURL = templ.URL(dashUrl + "/guestbook/themes")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">Themes</a></li><li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL = templ.URL(dashUrl + "/guestbook/customize")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">Custom CSS</a></li></ul></div><div><h3>Feeds</h3><p>Coming Soon</p></div><div><h3>Account</h3><ul><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"/users/privacy\">Privacy</a></li><li><a href=\"/help\">Help</a></li></ul></div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func displayWebsites(websites []models.Website) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if len(websites) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p>No Websites yet. <a href=\"\">Register a website.</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<ul id=\"websites\" hx-get=\"/websites\" hx-trigger=\"newWebsite from:body\" hx-swap=\"outerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, w := range websites {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(wUrl(w) + "/dashboard")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(w.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 57, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<input type=\"hidden\" name=\"csrf_token\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(csrfToken)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 65, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
err, exists := form.FieldErrors["sitename"]
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<label for=\"sitename\">Site Name: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if exists {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<label class=\"error\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 70, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<input type=\"text\" name=\"sitename\" id=\"sitename\" required></div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
err, exists = form.FieldErrors["siteurl"]
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<label for=\"siteurl\">Site URL: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if exists {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<label class=\"error\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 78, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<input type=\"text\" name=\"siteurl\" id=\"siteurl\" required></div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
err, exists = form.FieldErrors["authorname"]
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<label for=\"authorname\">Site Author: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if exists {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<label class=\"error\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 86, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<input type=\"text\" name=\"authorname\" id=\"authorname\" required></div><div><button type=\"submit\">Submit</button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func WebsiteCreateButton() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
if templ_7745c5c3_Var19 == nil {
templ_7745c5c3_Var19 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<button hx-get=\"/websites/create\" hx-target=\"closest div\">Add Website</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func WebsiteList(title string, data CommonData, websites []models.Website) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var20 := templ.GetChildren(ctx)
if templ_7745c5c3_Var20 == nil {
templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if data.IsHtmx {
templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<h1>My Websites</h1><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = WebsiteCreateButton().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func WebsiteDashboard(title string, data CommonData, website models.Website) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var22 := templ.GetChildren(ctx)
if templ_7745c5c3_Var22 == nil {
templ_7745c5c3_Var22 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var23 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div id=\"dashboard\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = wSidebar(website).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<div><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 120, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</h1><p>Stats and stuff will go here.</p></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var23), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var25 := templ.GetChildren(ctx)
if templ_7745c5c3_Var25 == nil {
templ_7745c5c3_Var25 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if data.IsHtmx {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<form hx-post=\"/websites/create\" hx-target=\"closest div\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = websiteCreateForm(data.CSRFToken, form).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<form action=\"/websites/create\" method=\"post\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = websiteCreateForm(data.CSRFToken, form).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1,41 @@
<nav><div><ul><li><a href=\"
\">Dashboard</a></li><li><a href=\"
\">View Website</a></li></ul><h3>Guestbook</h3><ul><li><a href=\"
\" target=\"_blank\">View Guestbook</a></li></ul><ul><li><a href=\"
\">Manage messages</a></li><li><a href=\"
\">Review message queue</a></li><li><a href=\"
\">Block users</a></li><li><a href=\"
\">Trash</a></li></ul><ul><li><a href=\"
\">Themes</a></li><li><a href=\"
\">Custom CSS</a></li></ul></div><div><h3>Feeds</h3><p>Coming Soon</p></div><div><h3>Account</h3><ul><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"/users/privacy\">Privacy</a></li><li><a href=\"/help\">Help</a></li></ul></div></nav>
<p>No Websites yet. <a href=\"\">Register a website.</a></p>
<ul id=\"websites\" hx-get=\"/websites\" hx-trigger=\"newWebsite from:body\" hx-swap=\"outerHTML\">
<li><a href=\"
\">
</a></li>
</ul>
<input type=\"hidden\" name=\"csrf_token\" value=\"
\"><div>
<label for=\"sitename\">Site Name: </label>
<label class=\"error\">
</label>
<input type=\"text\" name=\"sitename\" id=\"sitename\" required></div><div>
<label for=\"siteurl\">Site URL: </label>
<label class=\"error\">
</label>
<input type=\"text\" name=\"siteurl\" id=\"siteurl\" required></div><div>
<label for=\"authorname\">Site Author: </label>
<label class=\"error\">
</label>
<input type=\"text\" name=\"authorname\" id=\"authorname\" required></div><div><button type=\"submit\">Submit</button></div>
<button hx-get=\"/websites/create\" hx-target=\"closest div\">Add Website</button>
<h1>My Websites</h1><div>
</div><div>
</div>
<div id=\"dashboard\">
<div><h1>
</h1><p>Stats and stuff will go here.</p></div></div>
<form hx-post=\"/websites/create\" hx-target=\"closest div\">
</form>
<form action=\"/websites/create\" method=\"post\">
</form>