10-WebsiteModel #13
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -28,3 +28,6 @@ go.work
 | 
			
		||||
.air.toml
 | 
			
		||||
/tmp
 | 
			
		||||
tls/
 | 
			
		||||
test.db.old
 | 
			
		||||
.gitignore
 | 
			
		||||
.nvim/session
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										92
									
								
								cmd/web/handlers_website.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								cmd/web/handlers_website.go
									
									
									
									
									
										Normal 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)
 | 
			
		||||
}
 | 
			
		||||
@ -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",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										129
									
								
								cmd/web/main.go
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								cmd/web/main.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
CREATE TABLE sessions (
 | 
			
		||||
    token CHAR(43) primary key,
 | 
			
		||||
    data BLOB NOT NULL,
 | 
			
		||||
    expiry TEXT NOT NULL
 | 
			
		||||
);
 | 
			
		||||
@ -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
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -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:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -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
									
								
							
							
						
						
									
										102
									
								
								internal/models/website.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
<button hx-get="/guestbooks/create" hx-target="closest">New Guestbook</button>
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
{{ with .Guestbook }}
 | 
			
		||||
<li>
 | 
			
		||||
    <a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a>
 | 
			
		||||
</li>
 | 
			
		||||
{{ end }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -1,4 +0,0 @@
 | 
			
		||||
{{ define "title" }}Create a Guestbook{{ end }}
 | 
			
		||||
{{ define "main" }}
 | 
			
		||||
{{ template "guestbookcreate" }}
 | 
			
		||||
{{ end }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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}}
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
{{ define "title" }}{{ .User.Username }}{{ end }}
 | 
			
		||||
{{ define "main" }}
 | 
			
		||||
<h1>{{ .User.Username }}</h1>
 | 
			
		||||
<p>{{ .User.Email }}</p>
 | 
			
		||||
{{ end }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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 }}
 | 
			
		||||
@ -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 }>
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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
											
										
									
								
							@ -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>
 | 
			
		||||
@ -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>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -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
									
								
							
							
						
						
									
										139
									
								
								ui/views/websites.templ
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										544
									
								
								ui/views/websites_templ.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										41
									
								
								ui/views/websites_templ.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								ui/views/websites_templ.txt
									
									
									
									
									
										Normal 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>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user