Move handler objects to common location to share with RMO

This commit is contained in:
Eli Ribble 2026-03-03 17:08:58 +00:00
parent 87fe5ec2e5
commit 0f6da8e25f
No known key found for this signature in database
33 changed files with 449 additions and 308 deletions

15
html/config.go Normal file
View file

@ -0,0 +1,15 @@
package html
import (
"github.com/Gleipnir-Technology/nidus-sync/config"
)
type ContentConfig struct {
IsProductionEnvironment bool
}
func NewContentConfig() ContentConfig {
return ContentConfig{
IsProductionEnvironment: config.IsProductionEnvironment(),
}
}

7
html/content.go Normal file
View file

@ -0,0 +1,7 @@
package html
type Content[T any] struct {
C T
Config ContentConfig
URL ContentURL
}

26
html/handler.go Normal file
View file

@ -0,0 +1,26 @@
package html
import (
"context"
"net/http"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/rs/zerolog/log"
)
type handlerFunctionGet[T any] func(context.Context, *http.Request) (*Response[T], *nhttp.ErrorWithStatus)
func MakeGet[T any](f handlerFunctionGet[T]) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
resp, e := f(ctx, r)
if e != nil {
log.Warn().Int("status", e.Status)
http.Error(w, e.Error(), e.Status)
return
}
RenderOrError(w, resp.Template, Content[T]{
C: resp.Content,
})
}
}

View file

@ -38,3 +38,15 @@ func RespondError(w http.ResponseWriter, m string, e error, s int) {
log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error")
http.Error(w, m, s) http.Error(w, m, s)
} }
type Response[T any] struct {
Content T
Template string
}
func NewResponse[T any](template string, content T) *Response[T] {
return &Response[T]{
Content: content,
Template: template,
}
}

View file

@ -1,4 +1,4 @@
package sync package html
import ( import (
"strconv" "strconv"
@ -6,7 +6,7 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/config"
) )
type contentURL struct { type ContentURL struct {
Configuration contentURLConfiguration Configuration contentURLConfiguration
OAuthRefreshArcGIS string OAuthRefreshArcGIS string
Root string Root string
@ -16,8 +16,8 @@ type contentURL struct {
Upload contentURLUpload Upload contentURLUpload
} }
func newContentURL() contentURL { func NewContentURL() ContentURL {
return contentURL{ return ContentURL{
Configuration: newContentURLConfiguration(), Configuration: newContentURLConfiguration(),
OAuthRefreshArcGIS: config.MakeURLNidus("/arcgis/oauth/begin"), OAuthRefreshArcGIS: config.MakeURLNidus("/arcgis/oauth/begin"),
Root: config.MakeURLNidus("/"), Root: config.MakeURLNidus("/"),

32
http/error_with_status.go Normal file
View file

@ -0,0 +1,32 @@
package http
import (
"fmt"
"net/http"
)
type ErrorWithStatus struct {
Message string
Status int
}
func (e *ErrorWithStatus) Error() string {
return e.Message
}
func NewError(mesg_format string, args ...any) *ErrorWithStatus {
return NewErrorStatus(http.StatusInternalServerError, mesg_format, args...)
}
func NewErrorMaybe(mesg_format string, err error, args ...any) *ErrorWithStatus {
if err == nil {
return nil
}
allArgs := append([]any{err}, args...)
return NewErrorStatus(http.StatusInternalServerError, mesg_format, allArgs...)
}
func NewErrorStatus(status int, mesg_format string, args ...any) *ErrorWithStatus {
w := fmt.Errorf(mesg_format, args...)
return &ErrorWithStatus{
Message: w.Error(),
Status: status,
}
}

View file

@ -1,12 +1,67 @@
package rmo package rmo
import ( import (
"context"
"net/http" "net/http"
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/go-chi/chi/v5"
"github.com/stephenafamo/scan"
//"github.com/Gleipnir-Technology/nidus-sync/config" //"github.com/Gleipnir-Technology/nidus-sync/config"
) )
type contentMailer struct{} type address struct {
Number int32 `db:"number_"`
Street string `db:"street"`
Locality string `db:"locality"`
PostalCode string `db:"postal_code"`
Country string `db:"country"`
}
type contentMailer struct {
Address address
}
func getMailer(w http.ResponseWriter, r *http.Request) { func getMailer(ctx context.Context, r *http.Request) (*html.Response[contentMailer], *nhttp.ErrorWithStatus) {
public_id := chi.URLParam(r, "public_id")
if public_id == "" {
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "No 'public_id' in the url params")
}
/*
compliance_request, err := models.ComplianceReportRequests.Query(
models.Preload.ComplianceReportRequest.Site(),
models.SelectWhere.ComplianceReportRequests.PublicID.EQ(public_id),
).One(ctx, db.PGInstance.BobDB)
if err != nil {
respondError(w, "failed to get compliance request", err, http.StatusBadRequest)
}
site := compliance_request.
*/
report, err := bob.One(ctx, db.PGInstance.BobDB, psql.Select(
sm.Columns(
"address.number_",
"address.street",
"address.locality",
"address.postal_code",
"address.country",
),
sm.From("compliance_report_request").As("crr"),
sm.InnerJoin("site").OnEQ(psql.Raw("crr.site_id"), psql.Raw("site.id")),
sm.InnerJoin("address").OnEQ(psql.Raw("site.address_id"), psql.Raw("address.id")),
sm.Where(psql.Raw("crr.public_id").EQ(psql.Arg(public_id))),
), scan.StructMapper[address]())
if err != nil {
return nil, nhttp.NewErrorStatus(http.StatusNotFound, "No compliance report with that public ID")
}
return html.NewResponse(
"rmo/mailer.html", contentMailer{
Address: report,
},
), nil
} }

View file

@ -32,7 +32,7 @@ func Router() chi.Router {
r.Get("/email/unsubscribe", getEmailUnsubscribe) r.Get("/email/unsubscribe", getEmailUnsubscribe)
r.Get("/email/unsubscribe/report/{report_id}", getEmailReportUnsubscribe) r.Get("/email/unsubscribe/report/{report_id}", getEmailReportUnsubscribe)
r.Get("/image/{uuid}", getImageByUUID) r.Get("/image/{uuid}", getImageByUUID)
r.Get("/mailer/{public_id}", getMailer) r.Get("/mailer/{public_id}", html.MakeGet(getMailer))
r.Route("/mock", addMockRoutes) r.Route("/mock", addMockRoutes)
r.Post("/register-notifications", postRegisterNotifications) r.Post("/register-notifications", postRegisterNotifications)
r.Get("/register-notifications-complete", getRegisterNotificationsComplete) r.Get("/register-notifications-complete", getRegisterNotificationsComplete)

View file

@ -5,11 +5,13 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentAdminDash struct{} type contentAdminDash struct{}
func getAdminDash(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentAdminDash], *errorWithStatus) { func getAdminDash(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentAdminDash], *nhttp.ErrorWithStatus) {
content := contentAdminDash{} content := contentAdminDash{}
return newResponse("sync/admin-dash.html", content), nil return html.NewResponse("sync/admin-dash.html", content), nil
} }

View file

@ -6,6 +6,8 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/h3utils" "github.com/Gleipnir-Technology/nidus-sync/h3utils"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/uber/h3-go/v4" "github.com/uber/h3-go/v4"
) )
@ -19,46 +21,46 @@ type contentCell struct {
Treatments []Treatment Treatments []Treatment
} }
func getCellDetails(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentCell], *errorWithStatus) { func getCellDetails(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentCell], *nhttp.ErrorWithStatus) {
cell_str := chi.URLParam(r, "cell") cell_str := chi.URLParam(r, "cell")
if cell_str == "" { if cell_str == "" {
return nil, newErrorStatus(http.StatusBadRequest, "There should always be a cell") return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "There should always be a cell")
} }
c, err := HexToInt64(cell_str) c, err := HexToInt64(cell_str)
if err != nil { if err != nil {
return nil, newErrorStatus(http.StatusBadRequest, "Cannot convert provided cell to uint64") return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Cannot convert provided cell to uint64")
} }
center, err := h3.Cell(c).LatLng() center, err := h3.Cell(c).LatLng()
if err != nil { if err != nil {
return nil, newError("Failed to get center: %w", err) return nil, nhttp.NewError("Failed to get center: %w", err)
} }
boundary, err := h3.Cell(c).Boundary() boundary, err := h3.Cell(c).Boundary()
if err != nil { if err != nil {
return nil, newError("Failed to get boundary: %w", err) return nil, nhttp.NewError("Failed to get boundary: %w", err)
} }
inspections, err := inspectionsByCell(ctx, org, h3.Cell(c)) inspections, err := inspectionsByCell(ctx, org, h3.Cell(c))
if err != nil { if err != nil {
return nil, newError("Failed to get inspections by cell: %w", err) return nil, nhttp.NewError("Failed to get inspections by cell: %w", err)
} }
geojson, err := h3utils.H3ToGeoJSON([]h3.Cell{h3.Cell(c)}) geojson, err := h3utils.H3ToGeoJSON([]h3.Cell{h3.Cell(c)})
if err != nil { if err != nil {
return nil, newError("Failed to get boundaries: %w", err) return nil, nhttp.NewError("Failed to get boundaries: %w", err)
} }
resolution := h3.Cell(c).Resolution() resolution := h3.Cell(c).Resolution()
sources, err := breedingSourcesByCell(ctx, org, h3.Cell(c)) sources, err := breedingSourcesByCell(ctx, org, h3.Cell(c))
if err != nil { if err != nil {
return nil, newError("Failed to get sources: %w", err) return nil, nhttp.NewError("Failed to get sources: %w", err)
} }
traps, err := trapsByCell(ctx, org, h3.Cell(c)) traps, err := trapsByCell(ctx, org, h3.Cell(c))
if err != nil { if err != nil {
return nil, newError("Failed to get traps: %w", err) return nil, nhttp.NewError("Failed to get traps: %w", err)
} }
treatments, err := treatmentsByCell(ctx, org, h3.Cell(c)) treatments, err := treatmentsByCell(ctx, org, h3.Cell(c))
if err != nil { if err != nil {
return nil, newError("Failed to get treatments: %w", err) return nil, nhttp.NewError("Failed to get treatments: %w", err)
} }
return newResponse("sync/cell.html", contentCell{ return html.NewResponse("sync/cell.html", contentCell{
BreedingSources: sources, BreedingSources: sources,
CellBoundary: boundary, CellBoundary: boundary,
Inspections: inspections, Inspections: inspections,

View file

@ -5,10 +5,12 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentCommunicationRoot struct{} type contentCommunicationRoot struct{}
func getCommunicationRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentCommunicationRoot], *errorWithStatus) { func getCommunicationRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentCommunicationRoot], *nhttp.ErrorWithStatus) {
return newResponse("sync/communication-root.html", contentCommunicationRoot{}), nil return html.NewResponse("sync/communication-root.html", contentCommunicationRoot{}), nil
} }

View file

@ -5,26 +5,17 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/arcgis" "github.com/Gleipnir-Technology/nidus-sync/arcgis"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
//"github.com/rs/zerolog/log" //"github.com/rs/zerolog/log"
) )
type contentConfig struct {
IsProductionEnvironment bool
}
func newContentConfig() contentConfig {
return contentConfig{
IsProductionEnvironment: config.IsProductionEnvironment(),
}
}
type contentConfigurationRoot struct{} type contentConfigurationRoot struct{}
func getConfigurationRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentConfigurationRoot], *errorWithStatus) { func getConfigurationRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentConfigurationRoot], *nhttp.ErrorWithStatus) {
return newResponse("sync/configuration/root.html", contentConfigurationRoot{}), nil return html.NewResponse("sync/configuration/root.html", contentConfigurationRoot{}), nil
} }
type contentSettingOrganization struct { type contentSettingOrganization struct {
@ -35,10 +26,10 @@ type contentSettingIntegration struct {
ArcGISOAuth *models.ArcgisOauthToken ArcGISOAuth *models.ArcgisOauthToken
} }
func getConfigurationOrganization(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingOrganization], *errorWithStatus) { func getConfigurationOrganization(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentSettingOrganization], *nhttp.ErrorWithStatus) {
org, err := u.Organization().One(ctx, db.PGInstance.BobDB) org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil { if err != nil {
return nil, newError("get organization: %w", err) return nil, nhttp.NewError("get organization: %w", err)
} }
/* /*
var district contentDistrict var district contentDistrict
@ -74,44 +65,44 @@ func getConfigurationOrganization(ctx context.Context, r *http.Request, org *mod
data := contentSettingOrganization{ data := contentSettingOrganization{
Organization: org, Organization: org,
} }
return newResponse("sync/configuration/organization.html", data), nil return html.NewResponse("sync/configuration/organization.html", data), nil
} }
func getConfigurationIntegration(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) { func getConfigurationIntegration(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentSettingIntegration], *nhttp.ErrorWithStatus) {
oauth, err := arcgis.GetOAuthForUser(ctx, u) oauth, err := arcgis.GetOAuthForUser(ctx, u)
if err != nil { if err != nil {
return nil, newError("Failed to get oauth: %w", err) return nil, nhttp.NewError("Failed to get oauth: %w", err)
} }
data := contentSettingIntegration{ data := contentSettingIntegration{
ArcGISOAuth: oauth, ArcGISOAuth: oauth,
} }
return newResponse("sync/configuration/integration.html", data), nil return html.NewResponse("sync/configuration/integration.html", data), nil
} }
func getConfigurationIntegrationArcgis(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) { func getConfigurationIntegrationArcgis(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentSettingIntegration], *nhttp.ErrorWithStatus) {
oauth, err := arcgis.GetOAuthForUser(ctx, u) oauth, err := arcgis.GetOAuthForUser(ctx, u)
if err != nil { if err != nil {
return nil, newError("Failed to get oauth: %w", err) return nil, nhttp.NewError("Failed to get oauth: %w", err)
} }
data := contentSettingIntegration{ data := contentSettingIntegration{
ArcGISOAuth: oauth, ArcGISOAuth: oauth,
} }
return newResponse("sync/configuration/integration-arcgis.html", data), nil return html.NewResponse("sync/configuration/integration-arcgis.html", data), nil
} }
type contentSettingPlaceholder struct{} type contentSettingPlaceholder struct{}
func getConfigurationPesticide(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) { func getConfigurationPesticide(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{} content := contentSettingPlaceholder{}
return newResponse("sync/configuration/pesticide.html", content), nil return html.NewResponse("sync/configuration/pesticide.html", content), nil
} }
func getConfigurationPesticideAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) { func getConfigurationPesticideAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{} content := contentSettingPlaceholder{}
return newResponse("sync/configuration/pesticide-add.html", content), nil return html.NewResponse("sync/configuration/pesticide-add.html", content), nil
} }
func getConfigurationUserAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) { func getConfigurationUserAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{} content := contentSettingPlaceholder{}
return newResponse("sync/configuration/user-add.html", content), nil return html.NewResponse("sync/configuration/user-add.html", content), nil
} }
func getConfigurationUserList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) { func getConfigurationUserList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{} content := contentSettingPlaceholder{}
return newResponse("sync/configuration/user-list.html", content), nil return html.NewResponse("sync/configuration/user-list.html", content), nil
} }

View file

@ -14,6 +14,7 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html" "github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -61,8 +62,8 @@ func getDistrict(w http.ResponseWriter, r *http.Request) {
html.RenderOrError(w, "sync/district.html", &context) html.RenderOrError(w, "sync/district.html", &context)
} }
func getLayoutTest(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentLayoutTest], *errorWithStatus) { func getLayoutTest(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentLayoutTest], *nhttp.ErrorWithStatus) {
return newResponse("sync/layout-test.html", contentLayoutTest{}), nil return html.NewResponse("sync/layout-test.html", contentLayoutTest{}), nil
} }
func getRoot(w http.ResponseWriter, r *http.Request) { func getRoot(w http.ResponseWriter, r *http.Request) {
@ -93,40 +94,40 @@ func getRoot(w http.ResponseWriter, r *http.Request) {
} }
} }
func getSource(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSource], *errorWithStatus) { func getSource(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentSource], *nhttp.ErrorWithStatus) {
globalid_s := chi.URLParam(r, "globalid") globalid_s := chi.URLParam(r, "globalid")
if globalid_s == "" { if globalid_s == "" {
return nil, newError("No globalid provided: %w", nil) return nil, nhttp.NewError("No globalid provided: %w", nil)
} }
globalid, err := uuid.Parse(globalid_s) globalid, err := uuid.Parse(globalid_s)
if err != nil { if err != nil {
return nil, newError("globalid is not a UUID: %w", nil) return nil, nhttp.NewError("globalid is not a UUID: %w", nil)
} }
userContent, err := contentForUser(r.Context(), user) userContent, err := contentForUser(r.Context(), user)
if err != nil { if err != nil {
return nil, newError("Failed to get user content: %w", err) return nil, nhttp.NewError("Failed to get user content: %w", err)
} }
s, err := sourceByGlobalId(r.Context(), org, globalid) s, err := sourceByGlobalId(r.Context(), org, globalid)
if err != nil { if err != nil {
return nil, newError("Failed to get source: %w", err) return nil, nhttp.NewError("Failed to get source: %w", err)
} }
inspections, err := inspectionsBySource(r.Context(), org, globalid) inspections, err := inspectionsBySource(r.Context(), org, globalid)
if err != nil { if err != nil {
return nil, newError("Failed to get inspections: %w", err) return nil, nhttp.NewError("Failed to get inspections: %w", err)
} }
traps, err := trapsBySource(r.Context(), org, globalid) traps, err := trapsBySource(r.Context(), org, globalid)
if err != nil { if err != nil {
return nil, newError("Failed to get traps: %w", err) return nil, nhttp.NewError("Failed to get traps: %w", err)
} }
treatments, err := treatmentsBySource(r.Context(), org, globalid) treatments, err := treatmentsBySource(r.Context(), org, globalid)
if err != nil { if err != nil {
return nil, newError("Failed to get treatments: %w", err) return nil, nhttp.NewError("Failed to get treatments: %w", err)
} }
treatment_models := modelTreatment(treatments) treatment_models := modelTreatment(treatments)
latlng, err := s.H3Cell.LatLng() latlng, err := s.H3Cell.LatLng()
if err != nil { if err != nil {
return nil, newError("Failed to get latlng: %w", err) return nil, nhttp.NewError("Failed to get latlng: %w", err)
} }
data := contentSource{ data := contentSource{
Inspections: inspections, Inspections: inspections,
@ -148,40 +149,40 @@ func getSource(ctx context.Context, r *http.Request, org *models.Organization, u
User: userContent, User: userContent,
} }
return newResponse("sync/source.html", data), nil return html.NewResponse("sync/source.html", data), nil
} }
func getStadia(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentDashboard], *errorWithStatus) { func getStadia(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentDashboard], *nhttp.ErrorWithStatus) {
data := contentDashboard{ data := contentDashboard{
MapData: ComponentMap{ MapData: ComponentMap{
MapboxToken: config.MapboxToken, MapboxToken: config.MapboxToken,
}, },
} }
return newResponse("sync/stadia.html", data), nil return html.NewResponse("sync/stadia.html", data), nil
} }
func getTemplateTest(w http.ResponseWriter, r *http.Request) { func getTemplateTest(w http.ResponseWriter, r *http.Request) {
html.RenderOrError(w, "sync/template-test.html", nil) html.RenderOrError(w, "sync/template-test.html", nil)
} }
func getTrap(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentTrap], *errorWithStatus) { func getTrap(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentTrap], *nhttp.ErrorWithStatus) {
globalid_s := chi.URLParam(r, "globalid") globalid_s := chi.URLParam(r, "globalid")
if globalid_s == "" { if globalid_s == "" {
return nil, newError("No globalid provided: %w", nil) return nil, nhttp.NewError("No globalid provided: %w", nil)
} }
globalid, err := uuid.Parse(globalid_s) globalid, err := uuid.Parse(globalid_s)
if err != nil { if err != nil {
return nil, newError("globalid is not a UUID: %w", nil) return nil, nhttp.NewError("globalid is not a UUID: %w", nil)
} }
userContent, err := contentForUser(r.Context(), user) userContent, err := contentForUser(r.Context(), user)
if err != nil { if err != nil {
return nil, newError("Failed to get user content: %w", err) return nil, nhttp.NewError("Failed to get user content: %w", err)
} }
t, err := trapByGlobalId(r.Context(), org, globalid) t, err := trapByGlobalId(r.Context(), org, globalid)
if err != nil { if err != nil {
return nil, newError("Failed to get trap: %w", err) return nil, nhttp.NewError("Failed to get trap: %w", err)
} }
latlng, err := t.H3Cell.LatLng() latlng, err := t.H3Cell.LatLng()
if err != nil { if err != nil {
return nil, newError("Failed to get latlng: %w", err) return nil, nhttp.NewError("Failed to get latlng: %w", err)
} }
data := contentTrap{ data := contentTrap{
MapData: ComponentMap{ MapData: ComponentMap{
@ -198,7 +199,7 @@ func getTrap(ctx context.Context, r *http.Request, org *models.Organization, use
Trap: t, Trap: t,
User: userContent, User: userContent,
} }
return newResponse("sync/trap.html", data), nil return html.NewResponse("sync/trap.html", data), nil
} }
func dashboard(ctx context.Context, w http.ResponseWriter, org *models.Organization, user *models.User) { func dashboard(ctx context.Context, w http.ResponseWriter, org *models.Organization, user *models.User) {
@ -260,7 +261,7 @@ func dashboard(ctx context.Context, w http.ResponseWriter, org *models.Organizat
} }
html.RenderOrError(w, "sync/dashboard.html", contentAuthenticated[contentDashboard]{ html.RenderOrError(w, "sync/dashboard.html", contentAuthenticated[contentDashboard]{
C: content, C: content,
URL: newContentURL(), URL: html.NewContentURL(),
User: userContent, User: userContent,
}) })
} }

View file

@ -5,11 +5,13 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentDownloadPlaceholder struct{} type contentDownloadPlaceholder struct{}
func getDownloadList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentDownloadPlaceholder], *errorWithStatus) { func getDownloadList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentDownloadPlaceholder], *nhttp.ErrorWithStatus) {
content := contentDownloadPlaceholder{} content := contentDownloadPlaceholder{}
return newResponse("sync/download-list.html", content), nil return html.NewResponse("sync/download-list.html", content), nil
} }

119
sync/handler.go Normal file
View file

@ -0,0 +1,119 @@
package sync
import (
"context"
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/rs/zerolog/log"
)
type handlerFunctionGet[T any] func(context.Context, *http.Request, *models.Organization, *models.User) (*html.Response[T], *nhttp.ErrorWithStatus)
type wrappedHandler func(http.ResponseWriter, *http.Request)
type contentAuthenticated[T any] struct {
C T
Config html.ContentConfig
Organization *models.Organization
URL html.ContentURL
User User
}
// w http.ResponseWriter, r *http.Request, u *models.User) {
func authenticatedHandler[T any](f handlerFunctionGet[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) {
ctx := r.Context()
userContent, err := contentForUser(ctx, u)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if org == nil {
http.Error(w, "nil org", http.StatusInternalServerError)
return
}
resp, e := f(ctx, r, org, u)
//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 sync pages")
http.Error(w, e.Error(), e.Status)
return
}
html.RenderOrError(w, resp.Template, contentAuthenticated[T]{
C: resp.Content,
Config: html.NewContentConfig(),
Organization: org,
URL: html.NewContentURL(),
User: userContent,
})
})
}
type handlerFunctionPost[T any] func(context.Context, *http.Request, *models.Organization, *models.User, T) (string, *nhttp.ErrorWithStatus)
func authenticatedHandlerPost[T any](f handlerFunctionPost[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) {
err := r.ParseForm()
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
var content T
err = decoder.Decode(&content, r.PostForm)
if err != nil {
respondError(w, "Failed to decode form", err, http.StatusBadRequest)
return
}
ctx := r.Context()
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
path, e := f(ctx, r, org, u, content)
if e != nil {
http.Error(w, e.Error(), e.Status)
return
}
http.Redirect(w, r, path, http.StatusFound)
})
}
func authenticatedHandlerPostMultipart[T any](f handlerFunctionPost[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) {
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
var content T
err = decoder.Decode(&content, r.PostForm)
if err != nil {
respondError(w, "Failed to decode form", err, http.StatusBadRequest)
return
}
ctx := r.Context()
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
path, e := f(ctx, r, org, u, content)
if e != nil {
http.Error(w, e.Error(), e.Status)
return
}
http.Redirect(w, r, path, http.StatusFound)
})
}

View file

@ -5,10 +5,12 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentIntelligenceRoot struct{} type contentIntelligenceRoot struct{}
func getIntelligenceRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentIntelligenceRoot], *errorWithStatus) { func getIntelligenceRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentIntelligenceRoot], *nhttp.ErrorWithStatus) {
return newResponse("sync/intelligence-root.html", contentIntelligenceRoot{}), nil return html.NewResponse("sync/intelligence-root.html", contentIntelligenceRoot{}), nil
} }

View file

@ -16,7 +16,7 @@ import (
) )
type contentMailer struct { type contentMailer struct {
Config contentConfig Config html.ContentConfig
DocumentID string DocumentID string
LogoURL string LogoURL string
Organization *models.Organization Organization *models.Organization
@ -71,7 +71,7 @@ func getMailerPreview(w http.ResponseWriter, r *http.Request) {
} }
doc_id := uuid.New() doc_id := uuid.New()
html.RenderOrError(w, "sync/mailer.html", contentMailer{ html.RenderOrError(w, "sync/mailer.html", contentMailer{
Config: newContentConfig(), Config: html.NewContentConfig(),
DocumentID: doc_id.String(), DocumentID: doc_id.String(),
LogoURL: config.MakeURLNidus("/api/district/%s/logo", org.Slug.GetOr("unset")), LogoURL: config.MakeURLNidus("/api/district/%s/logo", org.Slug.GetOr("unset")),
Organization: org, Organization: org,

View file

@ -5,11 +5,13 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentMessageList struct{} type contentMessageList struct{}
func getMessageList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentMessageList], *errorWithStatus) { func getMessageList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentMessageList], *nhttp.ErrorWithStatus) {
content := contentMessageList{} content := contentMessageList{}
return newResponse("sync/message-list.html", content), nil return html.NewResponse("sync/message-list.html", content), nil
} }

View file

@ -54,7 +54,7 @@ func addMock(r chi.Router, path string, template string) {
} }
type contentMock struct { type contentMock struct {
Config contentConfig Config html.ContentConfig
DistrictName string DistrictName string
URLs ContentMockURLs URLs ContentMockURLs
} }
@ -66,7 +66,7 @@ func renderMock(template_name string) http.HandlerFunc {
code = "abc-123" code = "abc-123"
} }
data := contentMock{ data := contentMock{
Config: newContentConfig(), Config: html.NewContentConfig(),
DistrictName: "Delta MVCD", DistrictName: "Delta MVCD",
URLs: ContentMockURLs{ URLs: ContentMockURLs{
Dispatch: "/mock/dispatch", Dispatch: "/mock/dispatch",
@ -91,13 +91,13 @@ func renderMock(template_name string) http.HandlerFunc {
} }
type contentMockList struct { type contentMockList struct {
Config contentConfig Config html.ContentConfig
Mocks []mock Mocks []mock
} }
func renderMockList(w http.ResponseWriter, r *http.Request) { func renderMockList(w http.ResponseWriter, r *http.Request) {
data := contentMockList{ data := contentMockList{
Config: newContentConfig(), Config: html.NewContentConfig(),
Mocks: mocks, Mocks: mocks,
} }
html.RenderOrError(w, "sync/mock/root.html", data) html.RenderOrError(w, "sync/mock/root.html", data)

View file

@ -8,6 +8,8 @@ import (
//"time" //"time"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/notification" "github.com/Gleipnir-Technology/nidus-sync/notification"
//"github.com/Gleipnir-Technology/bob" //"github.com/Gleipnir-Technology/bob"
//"github.com/Gleipnir-Technology/bob/dialect/psql" //"github.com/Gleipnir-Technology/bob/dialect/psql"
@ -22,12 +24,12 @@ type contentNotificationList struct {
Notifications []notification.Notification Notifications []notification.Notification
} }
func getNotificationList(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentNotificationList], *errorWithStatus) { func getNotificationList(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentNotificationList], *nhttp.ErrorWithStatus) {
notifications, err := notification.ForUser(ctx, u) notifications, err := notification.ForUser(ctx, u)
if err != nil { if err != nil {
return nil, newError("Failed to get notifications: %w", err) return nil, nhttp.NewError("Failed to get notifications: %w", err)
} }
return newResponse("sync/notification-list.html", contentNotificationList{ return html.NewResponse("sync/notification-list.html", contentNotificationList{
Notifications: notifications, Notifications: notifications,
}), nil }), nil
} }

View file

@ -10,6 +10,8 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/background"
"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -65,7 +67,7 @@ func getArcgisOauthCallback(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config.MakeURLNidus("/"), http.StatusFound) http.Redirect(w, r, config.MakeURLNidus("/"), http.StatusFound)
} }
func getOAuthRefresh(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentOauthPrompt], *errorWithStatus) { func getOAuthRefresh(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentOauthPrompt], *nhttp.ErrorWithStatus) {
data := contentOauthPrompt{} data := contentOauthPrompt{}
return newResponse("sync/oauth-prompt.html", data), nil return html.NewResponse("sync/oauth-prompt.html", data), nil
} }

View file

@ -5,10 +5,12 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentOperationsRoot struct{} type contentOperationsRoot struct{}
func getOperationsRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentOperationsRoot], *errorWithStatus) { func getOperationsRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentOperationsRoot], *nhttp.ErrorWithStatus) {
return newResponse("sync/operations-root.html", contentOperationsRoot{}), nil return html.NewResponse("sync/operations-root.html", contentOperationsRoot{}), nil
} }

View file

@ -5,10 +5,12 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentParcel struct{} type contentParcel struct{}
func getParcel(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentParcel], *errorWithStatus) { func getParcel(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentParcel], *nhttp.ErrorWithStatus) {
return newResponse("sync/parcel.html", contentParcel{}), nil return html.NewResponse("sync/parcel.html", contentParcel{}), nil
} }

View file

@ -5,10 +5,12 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentPlanningRoot struct{} type contentPlanningRoot struct{}
func getPlanningRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPlanningRoot], *errorWithStatus) { func getPlanningRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentPlanningRoot], *nhttp.ErrorWithStatus) {
return newResponse("sync/planning-root.html", contentPlanningRoot{}), nil return html.NewResponse("sync/planning-root.html", contentPlanningRoot{}), nil
} }

View file

@ -5,16 +5,18 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentPoolList struct{} type contentPoolList struct{}
func getPoolList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPoolList], *errorWithStatus) { func getPoolList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentPoolList], *nhttp.ErrorWithStatus) {
return newResponse("sync/pool-list.html", contentPoolList{}), nil return html.NewResponse("sync/pool-list.html", contentPoolList{}), nil
} }
func getPoolCreate(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPoolList], *errorWithStatus) { func getPoolCreate(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentPoolList], *nhttp.ErrorWithStatus) {
return newResponse("sync/pool-upload.html", contentPoolList{}), nil return html.NewResponse("sync/pool-upload.html", contentPoolList{}), nil
} }
func getPoolByID(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPoolList], *errorWithStatus) { func getPoolByID(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentPoolList], *nhttp.ErrorWithStatus) {
return newResponse("sync/pool-by-id.html", contentPoolList{}), nil return html.NewResponse("sync/pool-by-id.html", contentPoolList{}), nil
} }

View file

@ -6,19 +6,21 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentRadar struct { type contentRadar struct {
Organization *models.Organization Organization *models.Organization
} }
func getRadar(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentRadar], *errorWithStatus) { func getRadar(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentRadar], *nhttp.ErrorWithStatus) {
org, err := user.Organization().One(ctx, db.PGInstance.BobDB) org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil { if err != nil {
return nil, newError("get org: %w", err) return nil, nhttp.NewError("get org: %w", err)
} }
data := contentRadar{ data := contentRadar{
Organization: org, Organization: org,
} }
return newResponse("sync/radar.html", data), nil return html.NewResponse("sync/radar.html", data), nil
} }

View file

@ -5,10 +5,12 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentReviewRoot struct{} type contentReviewRoot struct{}
func getReviewRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentReviewRoot], *errorWithStatus) { func getReviewRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentReviewRoot], *nhttp.ErrorWithStatus) {
return newResponse("sync/review-root.html", contentReviewRoot{}), nil return html.NewResponse("sync/review-root.html", contentReviewRoot{}), nil
} }

View file

@ -1,17 +1,10 @@
package sync package sync
import ( import (
"context"
"fmt"
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/api" "github.com/Gleipnir-Technology/nidus-sync/api"
"github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html" "github.com/Gleipnir-Technology/nidus-sync/html"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
) )
func Router() chi.Router { func Router() chi.Router {
@ -98,147 +91,3 @@ func Router() chi.Router {
html.AddStaticRoute(r, "/static") html.AddStaticRoute(r, "/static")
return r return r
} }
type errorWithStatus struct {
Message string
Status int
}
func (e *errorWithStatus) Error() string {
return e.Message
}
func newError(mesg_format string, args ...interface{}) *errorWithStatus {
return newErrorStatus(http.StatusInternalServerError, mesg_format, args...)
}
func newErrorMaybe(mesg_format string, err error, args ...interface{}) *errorWithStatus {
if err == nil {
return nil
}
allArgs := append([]interface{}{err}, args...)
return newErrorStatus(http.StatusInternalServerError, mesg_format, allArgs...)
}
func newErrorStatus(status int, mesg_format string, args ...interface{}) *errorWithStatus {
w := fmt.Errorf(mesg_format, args...)
return &errorWithStatus{
Message: w.Error(),
Status: status,
}
}
type response[T any] struct {
content T
template string
}
func newResponse[T any](template string, content T) *response[T] {
return &response[T]{
content: content,
template: template,
}
}
type handlerFunctionGet[T any] func(context.Context, *http.Request, *models.Organization, *models.User) (*response[T], *errorWithStatus)
type wrappedHandler func(http.ResponseWriter, *http.Request)
type contentAuthenticated[T any] struct {
C T
Config contentConfig
Organization *models.Organization
URL contentURL
User User
}
// w http.ResponseWriter, r *http.Request, u *models.User) {
func authenticatedHandler[T any](f handlerFunctionGet[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) {
ctx := r.Context()
userContent, err := contentForUser(ctx, u)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp, e := f(ctx, r, org, u)
//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 sync pages")
http.Error(w, e.Error(), e.Status)
return
}
if org == nil {
http.Error(w, "nil org", http.StatusInternalServerError)
return
}
html.RenderOrError(w, resp.template, contentAuthenticated[T]{
C: resp.content,
Config: newContentConfig(),
Organization: org,
URL: newContentURL(),
User: userContent,
})
})
}
type handlerFunctionPost[T any] func(context.Context, *http.Request, *models.Organization, *models.User, T) (string, *errorWithStatus)
func authenticatedHandlerPost[T any](f handlerFunctionPost[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) {
err := r.ParseForm()
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
var content T
err = decoder.Decode(&content, r.PostForm)
if err != nil {
respondError(w, "Failed to decode form", err, http.StatusBadRequest)
return
}
ctx := r.Context()
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
path, e := f(ctx, r, org, u, content)
if e != nil {
http.Error(w, e.Error(), e.Status)
return
}
http.Redirect(w, r, path, http.StatusFound)
})
}
func authenticatedHandlerPostMultipart[T any](f handlerFunctionPost[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) {
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
var content T
err = decoder.Decode(&content, r.PostForm)
if err != nil {
respondError(w, "Failed to decode form", err, http.StatusBadRequest)
return
}
ctx := r.Context()
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
path, e := f(ctx, r, org, u, content)
if e != nil {
http.Error(w, e.Error(), e.Status)
return
}
http.Redirect(w, r, path, http.StatusFound)
})
}

View file

@ -7,6 +7,8 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentActiveServiceRequest struct { type contentActiveServiceRequest struct {
@ -32,11 +34,11 @@ type contentServiceRequestList struct {
ClosedRequests []contentClosedServiceRequest ClosedRequests []contentClosedServiceRequest
} }
func getServiceRequestDetail(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentServiceRequestDetail], *errorWithStatus) { func getServiceRequestDetail(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentServiceRequestDetail], *nhttp.ErrorWithStatus) {
content := contentServiceRequestDetail{} content := contentServiceRequestDetail{}
return newResponse("sync/service-request-detail.html", content), nil return html.NewResponse("sync/service-request-detail.html", content), nil
} }
func getServiceRequestList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentServiceRequestList], *errorWithStatus) { func getServiceRequestList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentServiceRequestList], *nhttp.ErrorWithStatus) {
now := time.Now() now := time.Now()
content := contentServiceRequestList{ content := contentServiceRequestList{
ActiveRequests: []contentActiveServiceRequest{ ActiveRequests: []contentActiveServiceRequest{
@ -112,5 +114,5 @@ func getServiceRequestList(ctx context.Context, r *http.Request, org *models.Org
}, },
}, },
} }
return newResponse("sync/service-request-list.html", content), nil return html.NewResponse("sync/service-request-list.html", content), nil
} }

View file

@ -98,8 +98,8 @@ func postSignup(w http.ResponseWriter, r *http.Request) {
type contentUnauthenticated[T any] struct { type contentUnauthenticated[T any] struct {
C T C T
Config contentConfig Config html.ContentConfig
URL contentURL URL html.ContentURL
} }
func signin(w http.ResponseWriter, errorCode string, next string) { func signin(w http.ResponseWriter, errorCode string, next string) {
@ -111,8 +111,8 @@ func signin(w http.ResponseWriter, errorCode string, next string) {
InvalidCredentials: errorCode == "invalid-credentials", InvalidCredentials: errorCode == "invalid-credentials",
Next: next, Next: next,
}, },
Config: newContentConfig(), Config: html.NewContentConfig(),
URL: newContentURL(), URL: html.NewContentURL(),
} }
html.RenderOrError(w, "sync/signin.html", data) html.RenderOrError(w, "sync/signin.html", data)
} }

View file

@ -10,6 +10,8 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -19,9 +21,9 @@ type contentSudo struct {
ForwardEmailNidusAddress string ForwardEmailNidusAddress string
} }
func getSudo(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSudo], *errorWithStatus) { func getSudo(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentSudo], *nhttp.ErrorWithStatus) {
if user.Role != enums.UserroleRoot { if user.Role != enums.UserroleRoot {
return nil, &errorWithStatus{ return nil, &nhttp.ErrorWithStatus{
Message: "You have to be a root user to access this", Message: "You have to be a root user to access this",
Status: http.StatusForbidden, Status: http.StatusForbidden,
} }
@ -30,7 +32,7 @@ func getSudo(ctx context.Context, r *http.Request, org *models.Organization, use
ForwardEmailRMOAddress: config.ForwardEmailRMOAddress, ForwardEmailRMOAddress: config.ForwardEmailRMOAddress,
ForwardEmailNidusAddress: config.ForwardEmailNidusAddress, ForwardEmailNidusAddress: config.ForwardEmailNidusAddress,
} }
return newResponse("sync/sudo.html", content), nil return html.NewResponse("sync/sudo.html", content), nil
} }
var decoder = schema.NewDecoder() var decoder = schema.NewDecoder()
@ -42,9 +44,9 @@ type FormEmail struct {
To string `schema:"emailTo"` To string `schema:"emailTo"`
} }
func postSudoEmail(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, e FormEmail) (string, *errorWithStatus) { func postSudoEmail(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, e FormEmail) (string, *nhttp.ErrorWithStatus) {
if u.Role != enums.UserroleRoot { if u.Role != enums.UserroleRoot {
return "", &errorWithStatus{ return "", &nhttp.ErrorWithStatus{
Message: "You must have sudo powers to do this", Message: "You must have sudo powers to do this",
Status: http.StatusForbidden, Status: http.StatusForbidden,
} }
@ -71,9 +73,9 @@ type FormSMS struct {
Phone string `schema:"smsPhone"` Phone string `schema:"smsPhone"`
} }
func postSudoSMS(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, sms FormSMS) (string, *errorWithStatus) { func postSudoSMS(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, sms FormSMS) (string, *nhttp.ErrorWithStatus) {
if u.Role != enums.UserroleRoot { if u.Role != enums.UserroleRoot {
return "", &errorWithStatus{ return "", &nhttp.ErrorWithStatus{
Message: "You must have sudo powers to do this", Message: "You must have sudo powers to do this",
Status: http.StatusForbidden, Status: http.StatusForbidden,
} }

View file

@ -5,11 +5,13 @@ import (
"net/http" "net/http"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
) )
type contentTextMessages struct{} type contentTextMessages struct{}
func getTextMessages(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentTextMessages], *errorWithStatus) { func getTextMessages(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentTextMessages], *nhttp.ErrorWithStatus) {
content := contentTextMessages{} content := contentTextMessages{}
return newResponse("sync/text-messages.html", content), nil return html.NewResponse("sync/text-messages.html", content), nil
} }

View file

@ -8,10 +8,12 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/userfile" "github.com/Gleipnir-Technology/nidus-sync/userfile"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" //"github.com/rs/zerolog/log"
) )
type contentUploadList struct { type contentUploadList struct {
@ -19,11 +21,11 @@ type contentUploadList struct {
} }
type contentUploadPlaceholder struct{} type contentUploadPlaceholder struct{}
func getUploadList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentUploadList], *errorWithStatus) { func getUploadList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentUploadList], *nhttp.ErrorWithStatus) {
rows, err := platform.UploadSummaryList(ctx, org) rows, err := platform.UploadSummaryList(ctx, org)
return newResponse("sync/upload-list.html", contentUploadList{ return html.NewResponse("sync/upload-list.html", contentUploadList{
RecentUploads: rows, RecentUploads: rows,
}), newErrorMaybe("get upload list: %w", err) }), nhttp.NewErrorMaybe("get upload list: %w", err)
} }
type contentUploadDetail struct { type contentUploadDetail struct {
@ -36,110 +38,108 @@ type contentUploadPoolList struct {
} }
type contentUploadPool struct{} type contentUploadPool struct{}
func getUploadPool(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentUploadPool], *errorWithStatus) { func getUploadPool(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentUploadPool], *nhttp.ErrorWithStatus) {
data := contentUploadPool{} data := contentUploadPool{}
return newResponse("sync/upload-csv-pool.html", data), nil return html.NewResponse("sync/upload-csv-pool.html", data), nil
} }
type contentUploadPoolFlyoverCreate struct{} type contentUploadPoolFlyoverCreate struct{}
func getUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentUploadPoolFlyoverCreate], *errorWithStatus) { func getUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentUploadPoolFlyoverCreate], *nhttp.ErrorWithStatus) {
data := contentUploadPoolFlyoverCreate{} data := contentUploadPoolFlyoverCreate{}
return newResponse("sync/upload-csv-pool-flyover.html", data), nil return html.NewResponse("sync/upload-csv-pool-flyover.html", data), nil
} }
type contentUploadPoolCustomCreate struct{} type contentUploadPoolCustomCreate struct{}
func getUploadPoolCustomCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentUploadPoolCustomCreate], *errorWithStatus) { func getUploadPoolCustomCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentUploadPoolCustomCreate], *nhttp.ErrorWithStatus) {
data := contentUploadPoolCustomCreate{} data := contentUploadPoolCustomCreate{}
return newResponse("sync/upload-csv-pool-custom.html", data), nil return html.NewResponse("sync/upload-csv-pool-custom.html", data), nil
} }
func getUploadByID(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentUploadDetail], *errorWithStatus) { func getUploadByID(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*html.Response[contentUploadDetail], *nhttp.ErrorWithStatus) {
test := newContentURLUpload()
log.Info().Str("output", test.Discard(123)).Send()
file_id_str := chi.URLParam(r, "id") file_id_str := chi.URLParam(r, "id")
file_id_, err := strconv.ParseInt(file_id_str, 10, 32) file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil { if err != nil {
return nil, newError("Failed to parse file_id: %w", err) return nil, nhttp.NewError("Failed to parse file_id: %w", err)
} }
file_id := int32(file_id_) file_id := int32(file_id_)
detail, err := platform.GetUploadPoolDetail(ctx, u.OrganizationID, file_id) detail, err := platform.GetUploadPoolDetail(ctx, u.OrganizationID, file_id)
if err != nil { if err != nil {
return nil, newError("Failed to get pool: %w", err) return nil, nhttp.NewError("Failed to get pool: %w", err)
} }
data := contentUploadDetail{ data := contentUploadDetail{
CSVFileID: file_id, CSVFileID: file_id,
Organization: org, Organization: org,
Upload: detail, Upload: detail,
} }
return newResponse("sync/upload-by-id.html", data), nil return html.NewResponse("sync/upload-by-id.html", data), nil
} }
type FormUploadCommit struct{} type FormUploadCommit struct{}
func postUploadCommit(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadCommit) (string, *errorWithStatus) { func postUploadCommit(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadCommit) (string, *nhttp.ErrorWithStatus) {
file_id_str := chi.URLParam(r, "id") file_id_str := chi.URLParam(r, "id")
file_id_, err := strconv.ParseInt(file_id_str, 10, 32) file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil { if err != nil {
return "", newError("Failed to parse file_id: %w", err) return "", nhttp.NewError("Failed to parse file_id: %w", err)
} }
err = platform.UploadCommit(ctx, org, int32(file_id_)) err = platform.UploadCommit(ctx, org, int32(file_id_))
if err != nil { if err != nil {
return "", newError("Failed to mark discarded: %w", err) return "", nhttp.NewError("Failed to mark discarded: %w", err)
} }
return "/configuration/upload", nil return "/configuration/upload", nil
} }
type FormUploadDiscard struct{} type FormUploadDiscard struct{}
func postUploadDiscard(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadDiscard) (string, *errorWithStatus) { func postUploadDiscard(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadDiscard) (string, *nhttp.ErrorWithStatus) {
file_id_str := chi.URLParam(r, "id") file_id_str := chi.URLParam(r, "id")
file_id_, err := strconv.ParseInt(file_id_str, 10, 32) file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil { if err != nil {
return "", newError("Failed to parse file_id: %w", err) return "", nhttp.NewError("Failed to parse file_id: %w", err)
} }
err = platform.UploadDiscard(ctx, org, int32(file_id_)) err = platform.UploadDiscard(ctx, org, int32(file_id_))
if err != nil { if err != nil {
return "", newError("Failed to mark discarded: %w", err) return "", nhttp.NewError("Failed to mark discarded: %w", err)
} }
return "/configuration/upload", nil return "/configuration/upload", nil
} }
type FormUploadPool struct{} type FormUploadPool struct{}
func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadPool) (string, *errorWithStatus) { func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) {
uploads, err := userfile.SaveFileUpload(r, "csvfile", userfile.CollectionCSV) uploads, err := userfile.SaveFileUpload(r, "csvfile", userfile.CollectionCSV)
if err != nil { if err != nil {
return "", newError("Failed to extract image uploads: %s", err) return "", nhttp.NewError("Failed to extract image uploads: %s", err)
} }
if len(uploads) == 0 { if len(uploads) == 0 {
return "", newErrorStatus(http.StatusBadRequest, "No upload found") return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found")
} }
if len(uploads) != 1 { if len(uploads) != 1 {
return "", newErrorStatus(http.StatusBadRequest, "You must only submit one file at a time") return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
} }
upload := uploads[0] upload := uploads[0]
saved_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypeFlyover) saved_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypeFlyover)
if err != nil { if err != nil {
return "", newError("Failed to create new pool: %w", err) return "", nhttp.NewError("Failed to create new pool: %w", err)
} }
return fmt.Sprintf("/configuration/upload/%d", saved_upload.ID), nil return fmt.Sprintf("/configuration/upload/%d", saved_upload.ID), nil
} }
func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadPool) (string, *errorWithStatus) { func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) {
uploads, err := userfile.SaveFileUpload(r, "csvfile", userfile.CollectionCSV) uploads, err := userfile.SaveFileUpload(r, "csvfile", userfile.CollectionCSV)
if err != nil { if err != nil {
return "", newError("Failed to extract image uploads: %s", err) return "", nhttp.NewError("Failed to extract image uploads: %s", err)
} }
if len(uploads) == 0 { if len(uploads) == 0 {
return "", newErrorStatus(http.StatusBadRequest, "No upload found") return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found")
} }
if len(uploads) != 1 { if len(uploads) != 1 {
return "", newErrorStatus(http.StatusBadRequest, "You must only submit one file at a time") return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
} }
upload := uploads[0] upload := uploads[0]
pool_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypePoollist) pool_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypePoollist)
if err != nil { if err != nil {
return "", newError("Failed to create new pool: %w", err) return "", nhttp.NewError("Failed to create new pool: %w", err)
} }
return fmt.Sprintf("/configuration/upload/%d", pool_upload.ID), nil return fmt.Sprintf("/configuration/upload/%d", pool_upload.ID), nil
} }