package api import ( "crypto/rand" "encoding/hex" "encoding/json" "log/slog" "net/http" "strings" ) type MediaHandler struct { uploadDir string } func NewMediaHandler(uploadDir string) *MediaHandler { return &MediaHandler{uploadDir: uploadDir} } // HandleUpload handles POST /media — uploads a single file and returns its markdown reference. func (h *MediaHandler) HandleUpload(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(maxUploadSize); err != nil { http.Error(w, "too large", http.StatusRequestEntityTooLarge) return } fh, _, err := r.FormFile("file") if err != nil { http.Error(w, "missing file", http.StatusBadRequest) return } defer fh.Close() buf := make([]byte, 512) n, _ := fh.Read(buf) mime := http.DetectContentType(buf[:n]) if _, ok := allowedMIME[mime]; !ok { http.Error(w, "unsupported type", http.StatusUnsupportedMediaType) return } filename, err := saveUpload(h.uploadDir, randomID(), mime, buf[:n], fh) if err != nil { http.Error(w, "storage error", http.StatusInternalServerError) return } ref := markdownRef(mime, filename) slog.Info("media uploaded", "filename", filename, "mime", mime) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "filename": filename, "mime": mime, "ref": ref, }) } func markdownRef(mime, filename string) string { url := "/uploads/" + filename switch { case strings.HasPrefix(mime, "image/"): return "![" + filename + "](" + url + ")" case strings.HasPrefix(mime, "video/"): return "![" + filename + "](" + url + ")" case strings.HasPrefix(mime, "audio/"): return "[" + filename + "](" + url + ")" default: return "[" + filename + "](" + url + ")" } } func randomID() string { b := make([]byte, 12) _, _ = rand.Read(b) return hex.EncodeToString(b) }