Merge pull request 'feat: institutional membership tiers and corrected member directory' (#16) from devin/1778358341-institutional-membership-tiers into master
All checks were successful
phoenix-deploy Deployed to explorer-live
Deploy Explorer Live / deploy (push) Successful in 2m58s
All checks were successful
phoenix-deploy Deployed to explorer-live
Deploy Explorer Live / deploy (push) Successful in 2m58s
This commit was merged in pull request #16.
This commit is contained in:
89
backend/api/rest/membership.go
Normal file
89
backend/api/rest/membership.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/explorer/backend/auth"
|
||||
)
|
||||
|
||||
// handleMembershipTiers returns the canonical set of institutional tiers
|
||||
// with their labels and default explorer access tracks.
|
||||
// GET /api/v1/membership/tiers
|
||||
func (s *Server) handleMembershipTiers(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeMethodNotAllowed(w)
|
||||
return
|
||||
}
|
||||
|
||||
tiers := auth.ListTiers()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"tiers": tiers,
|
||||
})
|
||||
}
|
||||
|
||||
// handleMembershipMembers returns all active institutional members.
|
||||
// GET /api/v1/membership/members
|
||||
func (s *Server) handleMembershipMembers(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeMethodNotAllowed(w)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.requireDB(w) {
|
||||
return
|
||||
}
|
||||
|
||||
store := auth.NewMembershipStore(s.db)
|
||||
members, err := store.ListMembers(r.Context())
|
||||
if err != nil {
|
||||
writeInternalError(w, err.Error())
|
||||
return
|
||||
}
|
||||
if members == nil {
|
||||
members = []auth.InstitutionalMember{}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"members": members,
|
||||
"total": len(members),
|
||||
})
|
||||
}
|
||||
|
||||
// handleMembershipMemberDetail returns a single member by slug.
|
||||
// GET /api/v1/membership/members/{slug}
|
||||
func (s *Server) handleMembershipMemberDetail(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeMethodNotAllowed(w)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.requireDB(w) {
|
||||
return
|
||||
}
|
||||
|
||||
slug := strings.TrimPrefix(r.URL.Path, "/api/v1/membership/members/")
|
||||
if slug == "" {
|
||||
writeError(w, http.StatusBadRequest, "MISSING_SLUG", "Member slug is required")
|
||||
return
|
||||
}
|
||||
|
||||
store := auth.NewMembershipStore(s.db)
|
||||
member, err := store.GetMemberBySlug(r.Context(), slug)
|
||||
if err != nil {
|
||||
writeInternalError(w, err.Error())
|
||||
return
|
||||
}
|
||||
if member == nil {
|
||||
writeNotFound(w, "Member")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"member": member,
|
||||
})
|
||||
}
|
||||
@@ -67,6 +67,11 @@ func (s *Server) SetupRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/v1/access/usage", s.handleAccessUsage)
|
||||
mux.HandleFunc("/api/v1/access/audit", s.handleAccessAudit)
|
||||
|
||||
// Institutional membership directory (public, read-only)
|
||||
mux.HandleFunc("/api/v1/membership/tiers", s.handleMembershipTiers)
|
||||
mux.HandleFunc("/api/v1/membership/members", s.handleMembershipMembers)
|
||||
mux.HandleFunc("/api/v1/membership/members/", s.handleMembershipMemberDetail)
|
||||
|
||||
// Track 1 routes (public, optional auth)
|
||||
// Note: Track 1 endpoints should be registered with OptionalAuth middleware
|
||||
// mux.HandleFunc("/api/v1/track1/blocks/latest", s.track1Server.handleLatestBlocks)
|
||||
|
||||
Reference in New Issue
Block a user