interface improvements, use integer ids, add htmx for guestbook creation
This commit is contained in:
parent
e54875f943
commit
19364225c9
|
@ -161,10 +161,15 @@ func (app *application) getUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func (app *application) getGuestbookCreate(w http.ResponseWriter, r* http.Request) {
|
||||
data := app.newTemplateData(r)
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
app.renderHTMX(w, r, http.StatusOK, "guestbookcreate.part.html", data)
|
||||
return
|
||||
}
|
||||
app.render(w, r, http.StatusOK, "guestbookcreate.view.tmpl.html", data)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -172,23 +177,29 @@ func (app *application) postGuestbookCreate(w http.ResponseWriter, r* http.Reque
|
|||
}
|
||||
siteUrl := r.Form.Get("siteurl")
|
||||
shortId := app.createShortId()
|
||||
_, err = app.guestbooks.Insert(shortId, siteUrl, 0)
|
||||
_, 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)
|
||||
user, err := app.users.GetById(userId)
|
||||
if err != nil {
|
||||
app.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
user, err := app.users.GetById(userId)
|
||||
guestbooks, err := app.guestbooks.GetAll(userId)
|
||||
if err != nil {
|
||||
app.serverError(w, r, err)
|
||||
return
|
||||
|
@ -196,6 +207,10 @@ func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request)
|
|||
data := app.newTemplateData(r)
|
||||
data.Guestbooks = guestbooks
|
||||
data.User = user
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
app.renderHTMX(w, r, http.StatusCreated, "guestbooklist.part.html", data)
|
||||
return
|
||||
}
|
||||
app.render(w, r, http.StatusOK, "guestbooklist.view.tmpl.html", data)
|
||||
}
|
||||
|
||||
|
@ -312,3 +327,9 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt
|
|||
app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!")
|
||||
http.Redirect(w, r, fmt.Sprintf("/guestbooks/%s", guestbookSlug), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Request) {
|
||||
// slug := r.PathValue("id")
|
||||
// shortId := slugToShortId(slug)
|
||||
// app.guestbookComments.Delete(shortId)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
|
||||
"github.com/gorilla/schema"
|
||||
)
|
||||
|
||||
|
@ -25,6 +26,21 @@ func (app *application) clientError(w http.ResponseWriter, status int) {
|
|||
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) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
|
||||
ts, ok := app.templateCache[page]
|
||||
if !ok {
|
||||
|
@ -97,3 +113,14 @@ func (app *application) isAuthenticated(r *http.Request) bool {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ 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
|
||||
|
@ -48,6 +49,12 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
templateCacheHTMX, err := newHTMXTemplateCache()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sessionManager := scs.New()
|
||||
sessionManager.Store = sqlite3store.New(db)
|
||||
sessionManager.Lifetime = 12 * time.Hour
|
||||
|
@ -58,6 +65,7 @@ func main() {
|
|||
app := &application{
|
||||
sequence: 0,
|
||||
templateCache: templateCache,
|
||||
templateCacheHTMX: templateCacheHTMX,
|
||||
logger: logger,
|
||||
sessionManager: sessionManager,
|
||||
guestbooks: &models.GuestbookModel{DB: db},
|
||||
|
|
|
@ -79,8 +79,14 @@ func (app *application) authenticate(next http.Handler) http.Handler {
|
|||
app.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
user, err := app.users.GetById(id)
|
||||
if err != nil {
|
||||
app.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
ctx := context.WithValue(r.Context(), isAuthenticatedContextKey, true)
|
||||
ctx = context.WithValue(ctx, userNameContextKey, user)
|
||||
r = r.WithContext(ctx)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
|
|
|
@ -22,6 +22,7 @@ type templateData struct {
|
|||
Form any
|
||||
IsAuthenticated bool
|
||||
CSRFToken string
|
||||
CurrentUser *models.User
|
||||
}
|
||||
|
||||
func humanDate(t time.Time) string {
|
||||
|
@ -34,6 +35,23 @@ var functions = template.FuncMap {
|
|||
"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")
|
||||
|
@ -65,5 +83,6 @@ func (app *application) newTemplateData(r *http.Request) templateData {
|
|||
Flash: app.sessionManager.PopString(r.Context(), "flash"),
|
||||
IsAuthenticated: app.isAuthenticated(r),
|
||||
CSRFToken: nosurf.Token(r),
|
||||
CurrentUser: app.getCurrentUser(r),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ CREATE TABLE guestbooks (
|
|||
Id integer primary key autoincrement,
|
||||
ShortId integer UNIQUE NOT NULL,
|
||||
SiteUrl varchar(512) NOT NULL,
|
||||
UserId blob(16) NOT NULL,
|
||||
UserId integer NOT NULL,
|
||||
Created datetime NOT NULL,
|
||||
IsDeleted boolean NOT NULL DEFAULT FALSE,
|
||||
IsActive boolean NOT NULL DEFAULT TRUE,
|
||||
|
@ -25,8 +25,8 @@ CREATE TABLE guestbooks (
|
|||
CREATE TABLE guestbook_comments (
|
||||
Id integer primary key autoincrement,
|
||||
ShortId integer UNIQUE NOT NULL,
|
||||
GuestbookId blob(16) NOT NULL,
|
||||
ParentId blob(16),
|
||||
GuestbookId integer NOT NULL,
|
||||
ParentId integer,
|
||||
AuthorName varchar(256) NOT NULL,
|
||||
AuthorEmail varchar(256) NOT NULL,
|
||||
AuthorSite varchar(256),
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ template "title" }} - Guestbook</title>
|
||||
<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="/">Guestbook</a></h1>
|
||||
<h1><a href="/">webweav.ing</a></h1>
|
||||
</header>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<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>
|
|
@ -0,0 +1 @@
|
|||
<button hx-get="/guestbooks/create" hx-target="closest">New Guestbook</button>
|
|
@ -0,0 +1,7 @@
|
|||
<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>
|
|
@ -0,0 +1,5 @@
|
|||
{{ with .Guestbook }}
|
||||
<li>
|
||||
<a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a>
|
||||
</li>
|
||||
{{ end }}
|
|
@ -1,6 +1,7 @@
|
|||
{{ 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 }}
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
{{ define "title" }}Create a Guestbook{{ end }}
|
||||
{{ define "main" }}
|
||||
<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>
|
||||
{{ template "guestbookcreate" }}
|
||||
{{ end }}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
{{ define "title" }} Guestbooks {{ end }}
|
||||
{{ define "main" }}
|
||||
<h1>Guestbooks run by {{ .User.Username }}</h1>
|
||||
<ul>
|
||||
<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 }}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
{{ define "title" }}Home{{ end }}
|
||||
{{ define "main" }}
|
||||
<h2>Latest Guestbooks</h2>
|
||||
{{ if .IsAuthenticated }}
|
||||
<h2>Tools</h2>
|
||||
<p>
|
||||
<a href="/guestbooks">View Guestbooks</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/guestbooks/create">Create a Guestbook</a>
|
||||
<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 }}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{{ 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 }}
|
|
@ -6,8 +6,8 @@
|
|||
</div>
|
||||
<div>
|
||||
{{ if .IsAuthenticated }}
|
||||
{{ with .User }}
|
||||
<a href="/users/{{ .User.ShortId }}">{{ .User.Username }}</a>
|
||||
{{ with .CurrentUser }}
|
||||
Welcome, {{ .Username }}
|
||||
{{ end }}
|
||||
<form action="/users/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue