chore: fix upload error visibility + install.sh update mode ownership bug
This commit is contained in:
parent
422aaa08ab
commit
290f803996
2 changed files with 35 additions and 10 deletions
20
install.sh
20
install.sh
|
|
@ -73,9 +73,23 @@ if [ "${1:-}" = "-update" ]; then
|
|||
cd "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
# Determine the app user from the existing service file (most reliable).
|
||||
APP_USER=""
|
||||
if [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then
|
||||
APP_USER=$(grep -Po '^User=\K.*' "/etc/systemd/system/${SERVICE_NAME}.service" 2>/dev/null || true)
|
||||
fi
|
||||
# Fallback: check the owner of the install directory or binary.
|
||||
if [ -z "$APP_USER" ]; then
|
||||
APP_USER=$(stat -c '%U' "$INSTALL_DIR/app" 2>/dev/null || stat -c '%U' "$INSTALL_DIR" 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$APP_USER" ] || [ "$APP_USER" = "root" ]; then
|
||||
APP_USER="${SUDO_USER:-$(whoami)}"
|
||||
fi
|
||||
# Reset ownership so the app user can write files.
|
||||
APP_USER="${APP_USER:-${SUDO_USER:-$(whoami)}}"
|
||||
sudo_if chown -R "${APP_USER}:${APP_USER}" "$INSTALL_DIR" 2>/dev/null || true
|
||||
# Ensure key directories have correct permissions.
|
||||
sudo_if chmod 755 "$INSTALL_DIR" "$INSTALL_DIR/templates" "$INSTALL_DIR/static" 2>/dev/null || true
|
||||
sudo_if chmod 775 "$INSTALL_DIR/storage" 2>/dev/null || true
|
||||
|
||||
# Backup the database before making any changes.
|
||||
if [ -f "$INSTALL_DIR/expenses.db" ]; then
|
||||
|
|
@ -118,12 +132,12 @@ if [ "${1:-}" = "-update" ]; then
|
|||
export GOPATH="${INSTALL_DIR}/.go"
|
||||
export GOCACHE="${INSTALL_DIR}/.go/build"
|
||||
mkdir -p "${GOMODCACHE}" "${GOCACHE}" 2>/dev/null || sudo_if mkdir -p "${GOMODCACHE}" "${GOCACHE}"
|
||||
sudo_if chown -R "${APP_USER:-${SUDO_USER:-$(whoami)}}" "${INSTALL_DIR}/.go" "${INSTALL_DIR}/app" 2>/dev/null || true
|
||||
sudo_if chown -R "${APP_USER}" "${INSTALL_DIR}/.go" "${INSTALL_DIR}/app" 2>/dev/null || true
|
||||
sudo_if rm -f app
|
||||
CGO_ENABLED=0 go build -buildvcs=false -ldflags="-s -w" -o app .
|
||||
|
||||
# Fix ownership of all files (build cache may be root-owned from earlier builds).
|
||||
sudo_if chown -R "${APP_USER:-${SUDO_USER:-$(whoami)}}:${APP_USER:-${SUDO_USER:-$(whoami)}}" "$INSTALL_DIR" 2>/dev/null || true
|
||||
sudo_if chown -R "${APP_USER}:${APP_USER}" "$INSTALL_DIR" 2>/dev/null || true
|
||||
|
||||
info "Starting service..."
|
||||
sudo_if systemctl start "$SERVICE_NAME"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package handlers
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -59,7 +60,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: parse form: %v",
|
||||
time.Now().Format(time.RFC3339), err)
|
||||
http.Error(w, "Failed to parse upload form", http.StatusBadRequest)
|
||||
renderUploadError(w, "Failed to parse upload form.")
|
||||
return
|
||||
}
|
||||
defer r.MultipartForm.RemoveAll()
|
||||
|
|
@ -69,7 +70,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: missing receipt field: %v",
|
||||
time.Now().Format(time.RFC3339), err)
|
||||
http.Error(w, "Missing receipt file", http.StatusBadRequest)
|
||||
renderUploadError(w, "Missing receipt file.")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
|
@ -78,7 +79,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if header.Size > 10<<20 {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: file too large: %d bytes",
|
||||
time.Now().Format(time.RFC3339), header.Size)
|
||||
http.Error(w, "File too large. Maximum size is 10 MB.", http.StatusBadRequest)
|
||||
renderUploadError(w, "File too large. Maximum size is 10 MB.")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: read file: %v",
|
||||
time.Now().Format(time.RFC3339), err)
|
||||
http.Error(w, "Failed to read uploaded file", http.StatusInternalServerError)
|
||||
renderUploadError(w, "Failed to read uploaded file.")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if ext == "" {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: unsupported file type: %q",
|
||||
time.Now().Format(time.RFC3339), ext)
|
||||
http.Error(w, "Unsupported file format. Please upload a receipt image (JPEG, PNG, HEIC) or PDF.", http.StatusBadRequest)
|
||||
renderUploadError(w, "Unsupported file format. Please upload a receipt image (JPEG, PNG, HEIC) or PDF.")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +108,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if err := os.MkdirAll("storage", 0755); err != nil {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: mkdir storage: %v",
|
||||
time.Now().Format(time.RFC3339), err)
|
||||
http.Error(w, "Server error", http.StatusInternalServerError)
|
||||
renderUploadError(w, "Server error. Please try again.")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
|||
if err := os.WriteFile(storagePath, fileData, 0644); err != nil {
|
||||
log.Printf("ERROR [%s] handlers: UploadReceipt: write file: %v",
|
||||
time.Now().Format(time.RFC3339), err)
|
||||
http.Error(w, "Failed to save receipt image", http.StatusInternalServerError)
|
||||
renderUploadError(w, "Failed to save receipt image. Please try again.")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -586,6 +587,16 @@ func setCurrentEventID(w http.ResponseWriter, eventID string) {
|
|||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// renderUploadError writes an HTMX-compatible error fragment into the
|
||||
// #receipt-form container (the upload target). Uses HTTP 200 so HTMX
|
||||
// always swaps the content (HTMX skips 4xx/5xx by default).
|
||||
func renderUploadError(w http.ResponseWriter, message string) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `<div id="receipt-form"><div class="error-message" style="background: #450a0a; border: 1px solid #7f1d1d; color: #fca5a5; padding: 0.75rem; border-radius: 0.5rem; margin-bottom: 1rem;">%s</div></div>`,
|
||||
template.HTMLEscapeString(message))
|
||||
}
|
||||
|
||||
// detectImageExtension examines the magic bytes of the provided data to
|
||||
// determine its image format. Supports JPEG, PNG, WebP, GIF, BMP, TIFF,
|
||||
// and HEIC/HEIF (common on iPhones). Returns the file extension (without
|
||||
|
|
|
|||
Loading…
Reference in a new issue