fetch_ml/internal/api/validate/handlers.go
Jeremie Fraeys 7e5ceec069
feat(api): add groups and tokens handlers, refactor routes
Add new API endpoints and clean up handler interfaces:

- groups/handlers.go: New lab group management API
  * CRUD operations for lab groups
  * Member management with role assignment (admin/member/viewer)
  * Group listing and membership queries

- tokens/handlers.go: Token generation and validation endpoints
  * Create access tokens for public task sharing
  * Validate tokens for secure access
  * Token revocation and cleanup

- routes.go: Refactor handler registration
  * Integrate groups handler into WebSocket routes
  * Remove nil parameters from all handler constructors
  * Cleaner dependency injection pattern

- Handler interface cleanup across all modules:
  * jobs/handlers.go: Remove unused nil privacyEnforcer parameter
  * jupyter/handlers.go: Streamline initialization
  * scheduler/handlers.go: Consistent constructor signature
  * ws/handler.go: Add groups handler to dependencies
2026-03-08 12:51:25 -04:00

179 lines
5.4 KiB
Go

// Package validate provides WebSocket handlers for validation-related operations
package validate
import (
"errors"
"net/http"
"time"
"github.com/gorilla/websocket"
"github.com/jfraeys/fetch_ml/internal/api/helpers"
"github.com/jfraeys/fetch_ml/internal/auth"
"github.com/jfraeys/fetch_ml/internal/experiment"
"github.com/jfraeys/fetch_ml/internal/logging"
)
// Handler provides validation-related WebSocket handlers
type Handler struct {
expManager *experiment.Manager
logger *logging.Logger
authConfig *auth.Config
}
// NewHandler creates a new validate handler
func NewHandler(
expManager *experiment.Manager,
logger *logging.Logger,
authConfig *auth.Config,
) *Handler {
return &Handler{
expManager: expManager,
logger: logger,
authConfig: authConfig,
}
}
// Error codes
const (
ErrorCodeUnknownError = 0x00
ErrorCodeInvalidRequest = 0x01
ErrorCodeAuthenticationFailed = 0x02
ErrorCodePermissionDenied = 0x03
ErrorCodeResourceNotFound = 0x04
ErrorCodeValidationFailed = 0x40
)
// Permissions
const (
PermJobsRead = "jobs:read"
)
// sendErrorPacket sends an error response packet to the client
func (h *Handler) sendErrorPacket(conn *websocket.Conn, code byte, message, details string) error {
err := map[string]interface{}{
"error": true,
"code": code,
"message": message,
"details": details,
}
return conn.WriteJSON(err)
}
// sendSuccessPacket sends a success response packet
func (h *Handler) sendSuccessPacket(conn *websocket.Conn, data map[string]interface{}) error {
return conn.WriteJSON(data)
}
// ValidateRequest represents a validation request
// Protocol: [api_key_hash:16][validate_id_len:1][validate_id:var][commit_id:20]
type ValidateRequest struct {
ValidateID string
CommitID string
}
// ParseValidateRequest parses a validation request from the payload
func ParseValidateRequest(payload []byte) (*ValidateRequest, error) {
if len(payload) < 16+1+20 {
return nil, errors.New("validate request payload too short")
}
offset := 16
validateIDLen := int(payload[offset])
offset += 1
if validateIDLen <= 0 || len(payload) < offset+validateIDLen+20 {
return nil, errors.New("invalid validate id length")
}
validateID := string(payload[offset : offset+validateIDLen])
offset += validateIDLen
commitID := string(payload[offset : offset+20])
return &ValidateRequest{
ValidateID: validateID,
CommitID: commitID,
}, nil
}
// HandleValidate handles the validate WebSocket operation
func (h *Handler) HandleValidate(conn *websocket.Conn, payload []byte, user *auth.User) error {
req, err := ParseValidateRequest(payload)
if err != nil {
return h.sendErrorPacket(conn, ErrorCodeInvalidRequest, "invalid validate request", err.Error())
}
h.logger.Info("validation requested", "validate_id", req.ValidateID, "commit_id", req.CommitID, "user", user.Name)
// Validate commit ID format
if ok, errMsg := helpers.ValidateCommitIDFormat(req.CommitID); !ok {
return h.sendErrorPacket(conn, ErrorCodeInvalidRequest, "invalid commit_id format", errMsg)
}
// Validate experiment manifest
if ok, details := helpers.ValidateExperimentManifest(h.expManager, req.CommitID); !ok {
return h.sendErrorPacket(conn, ErrorCodeResourceNotFound, "experiment manifest validation failed", details)
}
// Create validation report
report := helpers.NewValidateReport()
report.CommitID = req.CommitID
report.TS = time.Now().UTC().Format(time.RFC3339)
// Add basic checks
report.Checks["commit_id_format"] = helpers.ValidateCheck{OK: true, Expected: "40 hex chars", Actual: req.CommitID}
report.Checks["manifest_exists"] = helpers.ValidateCheck{OK: true, Expected: "present", Actual: "found"}
return h.sendSuccessPacket(conn, map[string]interface{}{
"success": true,
"validate_id": req.ValidateID,
"commit_id": req.CommitID,
"report": report,
"timestamp": time.Now().UTC(),
})
}
// HandleGetValidateStatus handles getting the status of a validation
func (h *Handler) HandleGetValidateStatus(conn *websocket.Conn, validateID string, user *auth.User) error {
h.logger.Info("getting validation status", "validate_id", validateID, "user", user.Name)
// Stub implementation - in production, would query validation status from database
return h.sendSuccessPacket(conn, map[string]any{
"success": true,
"validate_id": validateID,
"status": "completed",
"timestamp": time.Now().UTC(),
})
}
// HandleListValidations handles listing all validations for a commit
func (h *Handler) HandleListValidations(conn *websocket.Conn, commitID string, user *auth.User) error {
h.logger.Info("listing validations", "commit_id", commitID, "user", user.Name)
// Stub implementation - in production, would query validations from database
return h.sendSuccessPacket(conn, map[string]any{
"success": true,
"commit_id": commitID,
"validations": []map[string]any{
{
"validate_id": "val-001",
"status": "completed",
"timestamp": time.Now().UTC(),
},
},
"count": 1,
})
}
// HTTP Handlers for REST API
// ValidateHTTP handles HTTP requests for validation
func (h *Handler) ValidateHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not implemented", http.StatusNotImplemented)
}
// GetValidationStatusHTTP handles HTTP requests for validation status
func (h *Handler) GetValidationStatusHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not implemented", http.StatusNotImplemented)
}