tuxedo notes: photo upload, username/email display + confirm-email, favicon; verified owner-only edit

This commit is contained in:
2026-08-07 19:46:08 +10:00
parent f7e018cf64
commit 0ee211d156
10 changed files with 105 additions and 4 deletions

View File

@@ -26,6 +26,10 @@ func (a *App) Register(w http.ResponseWriter, r *http.Request) {
a.render(w, r, "register", "Create account", nil, "Email and password are required.")
return
}
if confirm := strings.ToLower(strings.TrimSpace(r.FormValue("confirm_email"))); confirm != "" && confirm != email {
a.render(w, r, "register", "Create account", nil, "Emails do not match.")
return
}
if len(password) < 8 {
a.render(w, r, "register", "Create account", nil, "Password must be at least 8 characters.")
return

View File

@@ -2,7 +2,11 @@ package handlers
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
@@ -169,6 +173,73 @@ func (a *App) loadOwnedTag(w http.ResponseWriter, r *http.Request, uid int64) (d
return tag, true
}
// UploadPhoto saves an uploaded image for a tag and sets its photo_url (HTMX).
func (a *App) UploadPhoto(w http.ResponseWriter, r *http.Request) {
uid, _ := auth.GetUserID(r)
tag, ok := a.loadOwnedTag(w, r, uid)
if !ok {
return
}
if err := r.ParseMultipartForm(5 << 20); err != nil { // 5 MB
writeUploadMsg(w, "Upload too large (max 5 MB).")
return
}
file, header, err := r.FormFile("photo")
if err != nil {
writeUploadMsg(w, "Please choose an image file.")
return
}
defer file.Close()
if header.Size > 5<<20 {
writeUploadMsg(w, "Upload too large (max 5 MB).")
return
}
if !strings.HasPrefix(header.Header.Get("Content-Type"), "image/") {
writeUploadMsg(w, "Only image files are allowed.")
return
}
// Sanitise the filename and store under uploads/.
ext := strings.ToLower(filepath.Ext(header.Filename))
if ext == "" {
ext = ".jpg"
}
dst := filepath.Join("static", "uploads", fmt.Sprintf("tag%d%s", tag.ID, ext))
out, err := os.Create(dst)
if err != nil {
writeUploadMsg(w, "Could not save the photo.")
return
}
defer out.Close()
if _, err := io.Copy(out, file); err != nil {
writeUploadMsg(w, "Could not save the photo.")
return
}
photoURL := "/static/uploads/" + filepath.Base(dst)
if _, err := a.Queries.UpdateTagDetails(r.Context(), db.UpdateTagDetailsParams{
ID: tag.ID,
PhotoUrl: pgtype.Text{String: photoURL, Valid: true},
// Preserve existing values: re-send them via the form is not possible here,
// so use the stored tag fields for everything else.
ItemType: tag.ItemType, Description: tag.Description, Phone: tag.Phone,
Address: tag.Address, Notes: tag.Notes, SmsEnabled: tag.SmsEnabled,
}); err != nil {
writeUploadMsg(w, "Could not update the tag.")
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<p class="mt-2 text-xs text-green-700">Photo uploaded ✓ <a href="%s" class="underline">view</a> — refresh to see it on the tag page.</p>`, photoURL)
}
func writeUploadMsg(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<p class="mt-2 text-xs text-red-600">%s</p>`, msg)
}
func textOrNil(s string) pgtype.Text {
return pgtype.Text{String: s, Valid: s != ""}
}