tuxedo notes: photo upload, username/email display + confirm-email, favicon; verified owner-only edit
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 != ""}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user