Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/opportunities/handler.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/opportunities/handler.go |
| Original SHA-256 | d3cf8e4304fdcdb3840b9ee1473c1ed5fd7e204ae3c53d6217053cc4dba907de |
| Git blob SHA | c6cf42dc556f0ad7b8df9676f9af96334332c1e7 |
| Reading-copy SHA-256 | d3cf8e4304fdcdb3840b9ee1473c1ed5fd7e204ae3c53d6217053cc4dba907de |
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/opportunities/handler.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/opportunities/handler.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓111 lines · 3.1 KB
package opportunities
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
"github.com/missionworkforce/missionmatch/internal/platform/security"
)
// Handler serves the cached SAM opportunities read API.
type Handler struct{ svc *Service }
func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} }
// Routes mounts the opportunities endpoints under the /api group.
// Routes mounts the cached federal opportunity feed.
//
// Gated on candidate.read, which every role holds. The gate is not there to exclude anyone today; it is
// there so the endpoint states the authority it requires rather than inheriting it from whichever group
// it happens to be mounted in. A route moved to a different group later then fails closed.
func (h *Handler) Routes(r chi.Router) {
read := security.RequireCapability(security.CapCandidateRead)
r.With(read).Get("/opportunities", h.list)
r.With(read).Get("/opportunities/{noticeID}", h.get)
}
func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
f := Filters{
NAICS: multiVals(r, "naics"),
SetAsides: multiVals(r, "set_aside"),
States: multiVals(r, "state"),
Departments: multiVals(r, "department"),
SubAgencies: multiVals(r, "sub_agency"),
Q: strings.TrimSpace(r.URL.Query().Get("q")),
ActiveOnly: activeOnly(r),
Limit: intParam(r, "limit", defaultLimit),
}
items, err := h.svc.List(r.Context(), f)
if err != nil {
writeErr(w, err)
return
}
writeJSON(w, http.StatusOK, items)
}
func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "noticeID")
item, err := h.svc.Get(r.Context(), id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
writeJSON(w, http.StatusNotFound, map[string]string{"error": "opportunity not found"})
return
}
writeErr(w, err)
return
}
writeJSON(w, http.StatusOK, item)
}
// activeOnly defaults to true; only an explicit false/0/no turns it off.
func activeOnly(r *http.Request) bool {
switch strings.ToLower(strings.TrimSpace(r.URL.Query().Get("active_only"))) {
case "false", "0", "no":
return false
default:
return true
}
}
// multiVals collects a filter that may be repeated (?naics=a&naics=b) and/or comma-listed (?naics=a,b).
func multiVals(r *http.Request, key string) []string {
var out []string
for _, v := range r.URL.Query()[key] {
for _, p := range strings.Split(v, ",") {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
}
return out
}
func intParam(r *http.Request, key string, def int) int {
if v := strings.TrimSpace(r.URL.Query().Get(key)); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return def
}
func writeErr(w http.ResponseWriter, err error) {
if errors.Is(err, ErrNoDatabase) {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "database not configured"})
return
}
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}