Add district resource and an API to RMO

We're going to need an API for the single-page frontend
This commit is contained in:
Eli Ribble 2026-04-03 18:17:19 +00:00
parent 4f9617aa2f
commit bfecae7d61
No known key found for this signature in database
9 changed files with 114 additions and 16 deletions

View file

@ -110,9 +110,10 @@ func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
})
}
type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
func authenticatedHandlerJSONSlice[T any](f handlerFunctionGetSlice[T]) http.Handler {
func authenticatedHandlerJSONSlice[T any](f handlerFunctionGetSliceAuthenticated[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
var body []byte
@ -146,6 +147,40 @@ func authenticatedHandlerJSONSlice[T any](f handlerFunctionGetSlice[T]) http.Han
w.Write(body)
})
}
func handlerJSONSlice[T any](f handlerFunctionGetSlice[T]) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var body []byte
var params resource.QueryParams
err := decoder.Decode(&params, r.URL.Query())
if err != nil {
log.Error().Err(err).Msg("decode query failure")
http.Error(w, "failed to decode query", http.StatusInternalServerError)
return
}
resp, e := f(ctx, r, params)
w.Header().Set("Content-Type", "application/json")
//log.Info().Str("template", template).Err(e).Msg("handler done")
if e != nil {
log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
body, err = json.Marshal(ErrorAPI{Message: e.Error()})
if err != nil {
log.Error().Err(err).Msg("failed to marshal error")
http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
return
}
http.Error(w, string(body), e.Status)
return
}
body, err = json.Marshal(resp)
if err != nil {
log.Error().Err(err).Msg("failed to marshal json")
http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
return
}
w.Write(body)
}
}
type handlerFunctionPost[ReqType any] func(context.Context, *http.Request, ReqType) (string, *nhttp.ErrorWithStatus)
type handlerFunctionPostAuthenticated[RequestType any, ResponseType any] func(context.Context, *http.Request, platform.User, RequestType) (ResponseType, *nhttp.ErrorWithStatus)

View file

@ -66,8 +66,10 @@ func AddRoutes(r *mux.Router) {
r.Handle("/user/{id}", authenticatedHandlerJSONPut(user.ByIDPut)).Methods("PUT")
// Unauthenticated endpoints
r.HandleFunc("/district", apiGetDistrict).Methods("GET")
r.HandleFunc("/district/{slug}/logo", apiGetDistrictLogo).Methods("GET")
district := resource.District(router)
r.Handle("/district", handlerJSONSlice(district.List)).Methods("GET")
//r.HandleFunc("/district", apiGetDistrict).Methods("GET")
r.HandleFunc("/district/{slug}/logo", apiGetDistrictLogo).Methods("GET").Name("district.logo.BySlug")
r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET")
r.HandleFunc("/twilio/call", twilioCallPost).Methods("POST")
r.HandleFunc("/twilio/call/status", twilioCallStatusPost).Methods("POST")

View file

@ -120,7 +120,13 @@ func main() {
// Set up routing by hostname
nidussync.Router(sync_router)
sync_api_router := sync_router.PathPrefix("/api").Subrouter()
api.AddRoutes(sync_api_router)
rmo.Router(rmo_router)
rmo_api_router := rmo_router.PathPrefix("/api").Subrouter()
api.AddRoutes(rmo_api_router)
//hr.Map("", sr) // default
//hr.Map("*", sr) // default

View file

@ -83,6 +83,9 @@ func (o Organization) ServiceRequestRecent(ctx context.Context) ([]*models.Field
}
return results, nil
}
func (o Organization) Slug() string {
return o.model.Slug.GetOr("")
}
func OrganizationByID(ctx context.Context, id int) (*Organization, error) {
org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, int32(id))
if err != nil {
@ -94,10 +97,7 @@ func OrganizationByID(ctx context.Context, id int) (*Organization, error) {
o := newOrganization(org)
return &o, nil
}
func OrganizationList(ctx context.Context, user User) ([]*Organization, error) {
if !user.HasRoot() {
return []*Organization{&user.Organization}, nil
}
func OrganizationList(ctx context.Context) ([]*Organization, error) {
rows, err := models.Organizations.Query().All(ctx, db.PGInstance.BobDB)
if err != nil {
return nil, fmt.Errorf("query orgs: %w", err)

View file

@ -115,7 +115,7 @@ func UserList(ctx context.Context, user User) ([]*User, error) {
var orgByID map[int32]*Organization
if user.HasRoot() {
query = models.Users.Query()
orgs, err := OrganizationList(ctx, user)
orgs, err := OrganizationList(ctx)
if err != nil {
return nil, fmt.Errorf("org list: %w", err)
}

47
resource/district.go Normal file
View file

@ -0,0 +1,47 @@
package resource
import (
"context"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"net/http"
//"github.com/rs/zerolog/log"
)
type districtR struct {
router *router
}
type district struct {
Name string `json:"name"`
URLLogo string `json:"url_logo"`
}
func District(r *router) *districtR {
return &districtR{
router: r,
}
}
func (res *districtR) List(ctx context.Context, r *http.Request, query QueryParams) ([]*district, *nhttp.ErrorWithStatus) {
organizations, err := platform.OrganizationList(ctx)
if err != nil {
return nil, nhttp.NewError("list orgs: %w", err)
}
districts := make([]*district, 0)
for _, org := range organizations {
slug := org.Slug()
if slug == "" {
continue
}
logo, err := res.router.SlugToURI("district.logo.BySlug", slug)
if err != nil {
return nil, nhttp.NewError("logo url: %w", err)
}
districts = append(districts, &district{
Name: org.Name(),
URLLogo: logo,
})
}
return districts, nil
}

View file

@ -59,6 +59,18 @@ func (r *router) IDToURI(route string, id int) (string, error) {
uri.Scheme = "https"
return uri.String(), nil
}
func (r *router) SlugToURI(route string, slug string) (string, error) {
handler := r.router.Get(route)
if handler == nil {
return "", fmt.Errorf("nil handler '%s'", route)
}
uri, err := handler.URL("slug", slug)
if err != nil {
return "", fmt.Errorf("build uri: %w", err)
}
uri.Scheme = "https"
return uri.String(), nil
}
func (r *router) UUIDToURI(route string, u *uuid.UUID) (*string, error) {
if u == nil {

View file

@ -1,7 +1,6 @@
package sync
import (
"github.com/Gleipnir-Technology/nidus-sync/api"
"github.com/Gleipnir-Technology/nidus-sync/static"
"github.com/gorilla/mux"
)
@ -36,9 +35,6 @@ func Router(r *mux.Router) {
r.HandleFunc("/qr-code/mailer/{code}", getQRCodeMailer)
r.HandleFunc("/template-test", getTemplateTest)
api_router := r.PathPrefix("/api").Subrouter()
api.AddRoutes(api_router)
r.HandleFunc("/", getRoot)
r.HandleFunc("/_/*", getRoot)

View file

@ -60,12 +60,12 @@ export default defineConfig({
server: {
allowedHosts: ["poweredge.local", "dev-report.mosquitoes.online"],
port: 9001,
/*proxy: {
proxy: {
"/api": {
target: "http://127.0.0.1:9002",
target: "http://127.0.0.1:9003",
changeOrigin: false,
},
},*/
},
strictPort: true,
},
});