diff --git a/README.md b/README.md index 4ffcae0..8fb4bf0 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,16 @@ GET /api/cpone/database-settings GET /api/cpone/database-settings/{kodeRs} ``` +Daftar ringkas rumah sakit aktif dapat diambil tanpa memilih database HIS: + +```http +GET /api/cpone/hospitals +Authorization: Bearer +``` + +Response hanya memuat `kode_rs`, `nama`, dan `status`; rumah sakit nonaktif tidak +ditampilkan. + Saat startup, service otomatis membuat database `CPONE_DB_DATABASE` beserta tabel `hospitals` dan `hospital_databases`. Tidak ada database RS default; semua koneksi HIS harus ditambahkan lewat API. Akun `CPONE_DB_USERNAME` harus memiliki diff --git a/cmd/server/main.go b/cmd/server/main.go index b57ee47..c2c1e1f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -43,6 +43,9 @@ func main() { adminProtected := func(handler http.Handler) http.Handler { return apphttp.BearerAuth(cfg.DBSettingsToken, handler) } + catalogProtected := func(handler http.Handler) http.Handler { + return apphttp.BearerAuth(cfg.BearerToken, handler) + } mux := http.NewServeMux() mux.Handle("GET /api/cpone/paket", protected(http.HandlerFunc(handler.ListPaket))) @@ -62,6 +65,7 @@ func main() { mux.Handle("GET /api/cpone/database-settings", adminProtected(http.HandlerFunc(settingsHandler.List))) mux.Handle("GET /api/cpone/database-settings/{kodeRs}", adminProtected(http.HandlerFunc(settingsHandler.Get))) mux.Handle("POST /api/cpone/database-settings", adminProtected(http.HandlerFunc(settingsHandler.Store))) + mux.Handle("GET /api/cpone/hospitals", catalogProtected(http.HandlerFunc(settingsHandler.ListHospitals))) mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/internal/http/hospitals.go b/internal/http/hospitals.go new file mode 100644 index 0000000..0a78d4a --- /dev/null +++ b/internal/http/hospitals.go @@ -0,0 +1,29 @@ +package http + +import ( + "net/http" + + "primaya-api/cpone-middleware/internal/databaseconfig" +) + +type HospitalSummary struct { + RSCode string `json:"kode_rs"` + Name string `json:"nama"` + Status bool `json:"status"` +} + +func (h DatabaseSettingsHandler) ListHospitals(w http.ResponseWriter, _ *http.Request) { + settings := h.store.List() + hospitals := make([]HospitalSummary, 0, len(settings)) + for _, setting := range settings { + if !setting.Active { + continue + } + hospitals = append(hospitals, hospitalSummary(setting)) + } + WriteJSON(w, http.StatusOK, Response{Success: true, Message: "ok", Data: hospitals}) +} + +func hospitalSummary(setting databaseconfig.PublicSetting) HospitalSummary { + return HospitalSummary{RSCode: setting.RSCode, Name: setting.Name, Status: setting.Active} +} diff --git a/internal/http/hospitals_test.go b/internal/http/hospitals_test.go new file mode 100644 index 0000000..ad183bc --- /dev/null +++ b/internal/http/hospitals_test.go @@ -0,0 +1,50 @@ +package http + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "primaya-api/cpone-middleware/internal/databaseconfig" +) + +type fakeHospitalStore struct { + fakeDatabaseSettingsStore + settings []databaseconfig.PublicSetting +} + +func (f *fakeHospitalStore) List() []databaseconfig.PublicSetting { return f.settings } + +func TestListHospitalsOnlyReturnsActiveWithoutDatabaseCredentials(t *testing.T) { + store := &fakeHospitalStore{settings: []databaseconfig.PublicSetting{ + {RSCode: "AWALBROS_DEV", Name: "RS Dev Awalbros", Host: "secret-host", Username: "secret-user", Active: true}, + {RSCode: "INACTIVE_RS", Name: "RS Tidak Aktif", Host: "inactive-host", Active: false}, + }} + handler := NewDatabaseSettingsHandler(store) + recorder := httptest.NewRecorder() + handler.ListHospitals(recorder, httptest.NewRequest(http.MethodGet, "/api/cpone/hospitals", nil)) + + if recorder.Code != http.StatusOK { + t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) + } + for _, expected := range []string{`"kode_rs":"AWALBROS_DEV"`, `"nama":"RS Dev Awalbros"`, `"status":true`} { + if !strings.Contains(recorder.Body.String(), expected) { + t.Fatalf("response missing %s: %s", expected, recorder.Body.String()) + } + } + for _, forbidden := range []string{"INACTIVE_RS", "RS Tidak Aktif", "secret-host", "secret-user", `"host"`, `"username"`, `"password"`} { + if strings.Contains(recorder.Body.String(), forbidden) { + t.Fatalf("response contains %q: %s", forbidden, recorder.Body.String()) + } + } +} + +func TestListHospitalsReturnsEmptyArray(t *testing.T) { + handler := NewDatabaseSettingsHandler(&fakeHospitalStore{}) + recorder := httptest.NewRecorder() + handler.ListHospitals(recorder, httptest.NewRequest(http.MethodGet, "/api/cpone/hospitals", nil)) + if recorder.Body.String() != `{"success":true,"message":"ok","data":[]}`+"\n" { + t.Fatalf("body=%s", recorder.Body.String()) + } +}