Initial scaffold and technical specification

Made-with: Cursor
This commit is contained in:
Andrey Lutsenko
2026-04-09 21:40:04 +10:00
commit 284794ec8d
10 changed files with 487 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
package api
import (
"encoding/json"
"net/http"
"strings"
"time"
"nettopo-go/internal/scans"
)
type Handler struct {
store scans.Store
}
func NewHandler(store scans.Store) *Handler {
return &Handler{store: store}
}
func (h *Handler) Routes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", h.health)
mux.HandleFunc("POST /api/scans", h.createScan)
mux.HandleFunc("GET /api/scans/{id}", h.getScan)
return withJSON(mux)
}
func (h *Handler) health(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{
"status": "ok",
"time": time.Now().UTC(),
})
}
func (h *Handler) createScan(w http.ResponseWriter, r *http.Request) {
var req scans.CreateScanRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
req.Name = "manual-scan"
}
job, err := h.store.CreateScan(req)
if err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusCreated, map[string]any{
"scan_id": job.ID,
"status": job.Status,
})
}
func (h *Handler) getScan(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
writeError(w, http.StatusBadRequest, "scan id is required")
return
}
job, ok := h.store.GetScan(id)
if !ok {
writeError(w, http.StatusNotFound, "scan not found")
return
}
writeJSON(w, http.StatusOK, job)
}
func withJSON(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
next.ServeHTTP(w, r)
})
}
func writeJSON(w http.ResponseWriter, status int, payload any) {
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(payload)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}