feat: add active hospital catalog API
This commit is contained in:
10
README.md
10
README.md
@@ -183,6 +183,16 @@ GET /api/cpone/database-settings
|
|||||||
GET /api/cpone/database-settings/{kodeRs}
|
GET /api/cpone/database-settings/{kodeRs}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Daftar ringkas rumah sakit aktif dapat diambil tanpa memilih database HIS:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/cpone/hospitals
|
||||||
|
Authorization: Bearer <your-secure-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
Response hanya memuat `kode_rs`, `nama`, dan `status`; rumah sakit nonaktif tidak
|
||||||
|
ditampilkan.
|
||||||
|
|
||||||
Saat startup, service otomatis membuat database `CPONE_DB_DATABASE` beserta
|
Saat startup, service otomatis membuat database `CPONE_DB_DATABASE` beserta
|
||||||
tabel `hospitals` dan `hospital_databases`. Tidak ada database RS default; semua
|
tabel `hospitals` dan `hospital_databases`. Tidak ada database RS default; semua
|
||||||
koneksi HIS harus ditambahkan lewat API. Akun `CPONE_DB_USERNAME` harus memiliki
|
koneksi HIS harus ditambahkan lewat API. Akun `CPONE_DB_USERNAME` harus memiliki
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ func main() {
|
|||||||
adminProtected := func(handler http.Handler) http.Handler {
|
adminProtected := func(handler http.Handler) http.Handler {
|
||||||
return apphttp.BearerAuth(cfg.DBSettingsToken, handler)
|
return apphttp.BearerAuth(cfg.DBSettingsToken, handler)
|
||||||
}
|
}
|
||||||
|
catalogProtected := func(handler http.Handler) http.Handler {
|
||||||
|
return apphttp.BearerAuth(cfg.BearerToken, handler)
|
||||||
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("GET /api/cpone/paket", protected(http.HandlerFunc(handler.ListPaket)))
|
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", adminProtected(http.HandlerFunc(settingsHandler.List)))
|
||||||
mux.Handle("GET /api/cpone/database-settings/{kodeRs}", adminProtected(http.HandlerFunc(settingsHandler.Get)))
|
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("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) {
|
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|||||||
29
internal/http/hospitals.go
Normal file
29
internal/http/hospitals.go
Normal file
@@ -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}
|
||||||
|
}
|
||||||
50
internal/http/hospitals_test.go
Normal file
50
internal/http/hospitals_test.go
Normal file
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user