Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/recruitmatch/handler.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/recruitmatch/handler.go |
| Original SHA-256 | 364e6470327f2e34829e5224e6be8a17ce3f178edb92d5225710e1828d1c6767 |
| Git blob SHA | ebf48f7f800cc8b8031528921267f4d7004b633c |
| Reading-copy SHA-256 | 364e6470327f2e34829e5224e6be8a17ce3f178edb92d5225710e1828d1c6767 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/recruitmatch/handler.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/recruitmatch/handler.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓76 lines · 2.5 KB
package recruitmatch
import (
"encoding/json"
"errors"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/missionworkforce/missionmatch/internal/modules/matching"
"github.com/missionworkforce/missionmatch/internal/platform/security"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// Handler serves the recruitMATCH namespace. It holds the shared matching handler so the candidate and
// requirement pickers answer under this prefix exactly as they do under screenMATCH, slateMATCH and
// missionMATCH. That reuse is what makes the four tools one product rather than four silos.
type Handler struct {
core *matching.Handler
svc *Service
}
func NewHandler(core *matching.Handler, svc *Service) *Handler {
return &Handler{core: core, svc: svc}
}
// Routes mounts the dashboard read plus the shared pickers. The dashboard is gated on candidate.read
// because the payload exposes candidate counts and clearance distribution, the same data class as
// GET /candidates. That capability includes every role down to viewer, so the whole organization can see
// its own recruiting picture.
func (h *Handler) Routes(r chi.Router) {
r.With(security.RequireCapability(security.CapCandidateRead)).Get("/dashboard", h.get)
h.core.ReadRoutes(r)
}
func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
asOf := h.svc.clock()
if q := r.URL.Query().Get("as_of"); q != "" {
t, err := time.Parse("2006-01-02", q)
if err != nil {
// Deliberately a 400 rather than a silent fallback to now. A caller who asked for a specific
// day and quietly received today would draw conclusions from the wrong window.
writeJSONErr(w, http.StatusBadRequest, "as_of must be formatted 2006-01-02")
return
}
asOf = t
}
v, err := h.svc.RecruiterDashboard(r.Context(), asOf)
if err != nil {
switch {
case errors.Is(err, tenant.ErrNoTenant):
writeJSONErr(w, http.StatusForbidden, "no tenant context")
case errors.Is(err, ErrNoDatabase):
writeJSONErr(w, http.StatusServiceUnavailable, "database unavailable")
default:
// The wrapped error can carry table and column names, so it is logged upstream and never
// returned to the caller.
writeJSONErr(w, http.StatusInternalServerError, "internal error")
}
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(v); err != nil {
return
}
}
func writeJSONErr(w http.ResponseWriter, code int, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(map[string]string{"error": msg})
}