Corrections per 2026-04 institutional review:
- MLFO reclassified as Global Family Office (was incorrectly labeled central bank)
- BIS Innovation Hub reclassified as Standards Body (does not hold observer seat)
- Added missing entities: ICCC, SAID, PANDA, Order of Hospitallers (XOM)
- Added BRICS founding + expanded member central banks (10 entries)
New institutional tier taxonomy (7 tiers):
sovereign_central_bank, global_family_office, settlement_member,
infrastructure_operator, oversight_judicial, delegated_authority,
standards_body
Backend changes:
- New auth/membership.go: tier types, DefaultTrackForTier mapping,
MembershipStore with DB queries for member directory
- New migration 0017: institutional_members + institutional_member_wallets
tables with seed data for all corrected members
- Updated wallet_auth.go getUserTrack(): now resolves institutional
membership (via wallet junction table) before defaulting to Track 1
- WalletAuthResponse now includes institutional_tier and institution_name
- New REST endpoints: GET /api/v1/membership/{tiers,members,members/:slug}
- Added TrackLabel() helper in featureflags
Frontend changes:
- Added InstitutionalTier type and label map to access.ts
- WalletAccessSession extended with institutionalTier/institutionName
- Navbar getAccessTier() now displays institutional tier label when present
- Session summary shows institution name
Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
90 lines
2.0 KiB
Go
90 lines
2.0 KiB
Go
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,
|
|
})
|
|
}
|