diff --git a/server/api/api_server.go b/server/api/api_server.go index 68b4aedc7..5f03f0ac4 100644 --- a/server/api/api_server.go +++ b/server/api/api_server.go @@ -86,6 +86,7 @@ func (s *APIServer) Start() error { v2.HandleFunc("/version", s.Version).Methods("GET") v2.HandleFunc("/health", s.Health).Methods("GET") v2.HandleFunc("/server/status", s.ServerStatus).Methods("GET") + v2.HandleFunc("/server/info", s.ServerInfo).Methods("GET") // V2 authenticated routes v2Auth := v2.PathPrefix("").Subrouter() diff --git a/server/api/endpoints.go b/server/api/endpoints.go index c1519ad9c..29c4ae71e 100644 --- a/server/api/endpoints.go +++ b/server/api/endpoints.go @@ -156,6 +156,32 @@ func (s *APIServer) Version(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(resp) } +// ServerInfoResponse is the JSON payload returned by GET /v2/server/info. +// It exposes the server's configured game version in a form that launcher +// tools (e.g. mhf-outpost) can use to check version compatibility. +type ServerInfoResponse struct { + // ClientMode is the version string as configured in Erupe (e.g. "ZZ", "G10.1"). + ClientMode string `json:"clientMode"` + // ManifestID is the normalized form of ClientMode (lowercase, dots removed) + // matching mhf-outpost manifest IDs (e.g. "zz", "g101"). + ManifestID string `json:"manifestId"` + // Name is the server software name. + Name string `json:"name"` +} + +// ServerInfo handles GET /v2/server/info, returning the server's configured +// game version in a format compatible with mhf-outpost manifest IDs. +func (s *APIServer) ServerInfo(w http.ResponseWriter, r *http.Request) { + clientMode := s.erupeConfig.ClientMode + resp := ServerInfoResponse{ + ClientMode: clientMode, + ManifestID: strings.ToLower(strings.ReplaceAll(clientMode, ".", "")), + Name: "Erupe-CE", + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(resp) +} + // Launcher handles GET /launcher and returns banners, messages, and links for the launcher UI. func (s *APIServer) Launcher(w http.ResponseWriter, r *http.Request) { var respData LauncherResponse diff --git a/server/api/endpoints_coverage_test.go b/server/api/endpoints_coverage_test.go index 9eaa26e7a..4c520faa4 100644 --- a/server/api/endpoints_coverage_test.go +++ b/server/api/endpoints_coverage_test.go @@ -44,6 +44,56 @@ func TestVersionEndpoint(t *testing.T) { } } +func TestServerInfoEndpoint(t *testing.T) { + tests := []struct { + clientMode string + wantID string + }{ + {"ZZ", "zz"}, + {"GG", "gg"}, + {"G10.1", "g101"}, + {"G9.1", "g91"}, + {"FW.5", "fw5"}, + } + for _, tt := range tests { + t.Run(tt.clientMode, func(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + c.ClientMode = tt.clientMode + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("GET", "/v2/server/info", nil) + rec := httptest.NewRecorder() + server.ServerInfo(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } + if ct := rec.Header().Get("Content-Type"); ct != "application/json" { + t.Errorf("Content-Type = %q, want application/json", ct) + } + + var resp ServerInfoResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode error: %v", err) + } + if resp.ClientMode != tt.clientMode { + t.Errorf("ClientMode = %q, want %q", resp.ClientMode, tt.clientMode) + } + if resp.ManifestID != tt.wantID { + t.Errorf("ManifestID = %q, want %q", resp.ManifestID, tt.wantID) + } + if resp.Name != "Erupe-CE" { + t.Errorf("Name = %q, want Erupe-CE", resp.Name) + } + }) + } +} + func TestLandingPageEndpoint_Enabled(t *testing.T) { logger := NewTestLogger(t) c := NewTestConfig() diff --git a/server/api/routing_test.go b/server/api/routing_test.go index d72f7567b..a681f476c 100644 --- a/server/api/routing_test.go +++ b/server/api/routing_test.go @@ -44,6 +44,7 @@ func newTestRouter(s *APIServer) *mux.Router { v2Auth.HandleFunc("/characters/{id}/export", s.ExportSave).Methods("GET") v2.HandleFunc("/server/status", s.ServerStatus).Methods("GET") + v2.HandleFunc("/server/info", s.ServerInfo).Methods("GET") return r }