Initial commit
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
json "encoding/json/v2"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sourcedock.dev/petrbalvin/volumen/internal/i18n"
|
||||
"sourcedock.dev/petrbalvin/volumen/internal/imagefile"
|
||||
"sourcedock.dev/petrbalvin/volumen/internal/web"
|
||||
)
|
||||
|
||||
// writeAdminJSON writes one JSON object response; encoding/json escapes
|
||||
// what a browser's JSON.parse requires, which a %q verb does not.
|
||||
func writeAdminJSON(w http.ResponseWriter, status int, value map[string]any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.MarshalWrite(w, value, json.Deterministic(true)); err != nil {
|
||||
slog.Warn("admin: cannot encode JSON response", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Admin) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
if !a.requireCSRF(w, r) {
|
||||
return
|
||||
}
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
writeAdminJSONError(w, http.StatusBadRequest, "no_file", i18n.Admin.T(a.langFor(r), "No file was uploaded."))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
limit := int64(a.deps.Config.Admin.MaxUploadBytes)
|
||||
raw, err := readLimited(file, limit)
|
||||
if err != nil {
|
||||
writeAdminJSONError(w, http.StatusRequestEntityTooLarge, "too_large",
|
||||
i18n.Admin.Tf(a.langFor(r),
|
||||
"The file could not be read (limit %s bytes).",
|
||||
strconv.FormatInt(limit, 10)))
|
||||
return
|
||||
}
|
||||
if errMsg := validateImageData(raw); errMsg != "" {
|
||||
writeAdminJSONError(w, http.StatusUnsupportedMediaType, errMsg,
|
||||
i18n.Admin.T(a.langFor(r), "Only WebP, AVIF and SVG images are supported."))
|
||||
return
|
||||
}
|
||||
url, err := a.deps.Store.StoreUpload(header.Filename, raw)
|
||||
if err != nil {
|
||||
writeAdminJSONError(w, http.StatusInternalServerError, "upload_failed",
|
||||
i18n.Admin.T(a.langFor(r), "The upload could not be stored."))
|
||||
return
|
||||
}
|
||||
writeAdminJSON(w, http.StatusOK, map[string]any{"url": url})
|
||||
}
|
||||
|
||||
func writeAdminJSONError(w http.ResponseWriter, status int, code, message string) {
|
||||
writeAdminJSON(w, status, map[string]any{"error": code, "message": message})
|
||||
}
|
||||
|
||||
// validateImageData reports why data is not an acceptable upload. The
|
||||
// stored extension is taken from the byte signature, so the declared
|
||||
// filename's type is irrelevant: what matters is that the bytes are one
|
||||
// of the accepted image formats.
|
||||
func validateImageData(data []byte) string {
|
||||
if imagefile.Detect(data) == "" {
|
||||
return "invalid_signature"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Admin) handleIcon(w http.ResponseWriter, _ *http.Request) {
|
||||
a.serveStaticSVG(w, "volumen-icon.svg")
|
||||
}
|
||||
|
||||
func (a *Admin) serveStaticSVG(w http.ResponseWriter, name string) {
|
||||
data, err := web.StaticFile(name)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
Reference in New Issue
Block a user