mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-07-02 15:51:00 +02:00
feat: POST /api/waitlist — backend endpoint for registration waitlist
- server.go: route registration (public, rate-limited) - soc_handlers.go: handleWaitlist with email validation, input sanitization - service.go: AddWaitlistEntry with audit trail + structured logging - Frontend form at /register already submits to this endpoint
This commit is contained in:
parent
29a0116125
commit
413fa8aa2c
3 changed files with 79 additions and 0 deletions
|
|
@ -1436,3 +1436,17 @@ func (s *Service) ImportIncidents(incidents []peer.SyncIncident) (int, error) {
|
||||||
}
|
}
|
||||||
return imported, nil
|
return imported, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddWaitlistEntry records a waitlist registration interest.
|
||||||
|
// Currently logs to the audit trail — DB persistence added when registration opens.
|
||||||
|
func (s *Service) AddWaitlistEntry(email, company, useCase string) {
|
||||||
|
if s.logger != nil {
|
||||||
|
s.logger.Record(audit.ModuleSOC, "WAITLIST:NEW",
|
||||||
|
fmt.Sprintf("email=%s company=%s use_case=%s", email, company, useCase))
|
||||||
|
}
|
||||||
|
slog.Info("waitlist entry recorded",
|
||||||
|
"email", email,
|
||||||
|
"company", company,
|
||||||
|
"use_case", useCase,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,8 @@ func (s *Server) Start(ctx context.Context) error {
|
||||||
mux.HandleFunc("POST /api/v1/scan", s.handlePublicScan)
|
mux.HandleFunc("POST /api/v1/scan", s.handlePublicScan)
|
||||||
// Usage endpoint — returns scan quota for caller
|
// Usage endpoint — returns scan quota for caller
|
||||||
mux.HandleFunc("GET /api/v1/usage", s.handleUsage)
|
mux.HandleFunc("GET /api/v1/usage", s.handleUsage)
|
||||||
|
// Waitlist endpoint — registration interest capture (no auth, rate-limited)
|
||||||
|
mux.HandleFunc("POST /api/waitlist", s.handleWaitlist)
|
||||||
|
|
||||||
// pprof debug endpoints (§P4C) — gated behind EnablePprof()
|
// pprof debug endpoints (§P4C) — gated behind EnablePprof()
|
||||||
if s.pprofEnabled {
|
if s.pprofEnabled {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -1581,3 +1582,65 @@ func (s *Server) handleUsage(w http.ResponseWriter, r *http.Request) {
|
||||||
info := s.usageTracker.GetUsage(userID, ip)
|
info := s.usageTracker.GetUsage(userID, ip)
|
||||||
writeJSON(w, http.StatusOK, info)
|
writeJSON(w, http.StatusOK, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleWaitlist captures registration interest when signups are closed.
|
||||||
|
// POST /api/waitlist body: {"email": "user@corp.com", "company": "CorpX", "use_case": "LLM protection"}
|
||||||
|
// Public endpoint, no auth required. Rate-limited globally.
|
||||||
|
func (s *Server) handleWaitlist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
limitBody(w, r)
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Company string `json:"company"`
|
||||||
|
UseCase string `json:"use_case"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, "invalid JSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate email
|
||||||
|
if req.Email == "" || len(req.Email) < 5 || len(req.Email) > 254 {
|
||||||
|
writeError(w, http.StatusBadRequest, "valid email is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Basic email format check
|
||||||
|
hasAt := false
|
||||||
|
for _, c := range req.Email {
|
||||||
|
if c == '@' {
|
||||||
|
hasAt = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasAt {
|
||||||
|
writeError(w, http.StatusBadRequest, "valid email is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize
|
||||||
|
if len(req.Company) > 200 {
|
||||||
|
req.Company = req.Company[:200]
|
||||||
|
}
|
||||||
|
if len(req.UseCase) > 1000 {
|
||||||
|
req.UseCase = req.UseCase[:1000]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the waitlist entry (always — even if DB fails)
|
||||||
|
slog.Info("waitlist submission",
|
||||||
|
"email", req.Email,
|
||||||
|
"company", req.Company,
|
||||||
|
"use_case", req.UseCase,
|
||||||
|
"ip", r.RemoteAddr,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Persist via SOC repo if available
|
||||||
|
if s.socSvc != nil {
|
||||||
|
s.socSvc.AddWaitlistEntry(req.Email, req.Company, req.UseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
|
"status": "ok",
|
||||||
|
"message": "You've been added to the waitlist. We'll notify you when registration opens.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue