add bug report creation handler and update routing structure

This commit is contained in:
Flavio Fois
2026-03-17 18:20:14 +01:00
parent c61afa45c7
commit 45dbe480a5
3 changed files with 149 additions and 22 deletions

2
.gitignore vendored
View File

@@ -34,3 +34,5 @@ go.work.sum
# Claude
.claude/
tmp/

View File

@@ -7,7 +7,10 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/jmoiron/sqlx"
@@ -15,6 +18,125 @@ import (
"emly-api-go/internal/models"
)
var fileRoles = []struct {
field string
role models.FileRole
defaultMime string
}{
{"attachment", models.FileRoleAttachment, "application/octet-stream"},
{"screenshot", models.FileRoleScreenshot, "image/png"},
{"log", models.FileRoleLog, "text/plain"},
}
func CreateBugReport(db *sqlx.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(32 << 20); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"error": "invalid multipart form: " + err.Error()})
return
}
name := r.FormValue("name")
email := r.FormValue("email")
description := r.FormValue("description")
hwid := r.FormValue("hwid")
hostname := r.FormValue("hostname")
osUser := r.FormValue("os_user")
systemInfoStr := r.FormValue("system_info")
if name == "" || email == "" || description == "" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"error": "name, email and description are required"})
return
}
submitterIP := strings.TrimSpace(strings.SplitN(r.Header.Get("X-Forwarded-For"), ",", 2)[0])
if submitterIP == "" {
submitterIP = r.Header.Get("X-Real-IP")
}
if submitterIP == "" {
submitterIP = "unknown"
}
var systemInfo json.RawMessage
if systemInfoStr != "" && json.Valid([]byte(systemInfoStr)) {
systemInfo = json.RawMessage(systemInfoStr)
}
log.Printf("[BUGREPORT] Received from name=%s hwid=%s ip=%s", name, hwid, submitterIP)
result, err := db.ExecContext(r.Context(),
"INSERT INTO emly_bugreports.bug_reports (name, email, description, hwid, hostname, os_user, submitter_ip, system_info, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
name, email, description, hwid, hostname, osUser, submitterIP, systemInfo, models.BugReportStatusNew,
)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
reportID, err := result.LastInsertId()
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
for _, fr := range fileRoles {
file, header, err := r.FormFile(fr.field)
if err != nil {
// no file uploaded for this role — skip
continue
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": "reading file " + fr.field + ": " + err.Error()})
return
}
mimeType := header.Header.Get("Content-Type")
if mimeType == "" {
mimeType = fr.defaultMime
}
filename := header.Filename
if filename == "" {
filename = fr.field + ".bin"
}
log.Printf("[BUGREPORT] File uploaded: role=%s size=%d bytes", fr.role, len(data))
_, err = db.ExecContext(r.Context(),
"INSERT INTO emly_bugreports.bug_report_files (report_id, file_role, filename, mime_type, file_size, data) VALUES (?, ?, ?, ?, ?, ?)",
reportID, fr.role, filename, mimeType, len(data), data,
)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
}
log.Printf("[BUGREPORT] Created successfully with id=%d", reportID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"report_id": reportID,
"message": "Bug report submitted successfully",
})
}
}
func GetAllBugReports(db *sqlx.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var reports []models.BugReport

View File

@@ -1,6 +1,7 @@
package main
import (
apimw "emly-api-go/internal/middleware"
"fmt"
"log"
"net/http"
@@ -15,7 +16,6 @@ import (
"emly-api-go/internal/config"
"emly-api-go/internal/database"
"emly-api-go/internal/handlers"
apimw "emly-api-go/internal/middleware"
)
func main() {
@@ -52,7 +52,7 @@ func main() {
w.Write([]byte("emly-api-go"))
})
r.Route("/api/v1", func(r chi.Router) {
r.Route("/v1", func(r chi.Router) {
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Server", "emly-api-go")
@@ -63,6 +63,7 @@ func main() {
// Health public, no API key required
r.Get("/health", handlers.Health(db))
r.Route("/api", func(r chi.Router) {
// ROUTE: Bug Reports - Protected via API Key
r.Route("/bug-reports", func(r chi.Router) {
r.Group(func(r chi.Router) {
@@ -72,6 +73,7 @@ func main() {
r.Use(httprate.LimitByIP(30, time.Minute))
r.Get("/count", handlers.GetReportsCount(db))
r.Post("/", handlers.CreateBugReport(db))
})
r.Group(func(r chi.Router) {
@@ -89,6 +91,7 @@ func main() {
r.Get("/{id}/zip", handlers.GetBugReportZipById(db))
})
})
})
})