Move all POST endpoints to the API

This commit is contained in:
Eli Ribble 2026-03-27 06:08:55 -07:00
parent 3ff7ff05ab
commit d7c07fc65f
No known key found for this signature in database
19 changed files with 466 additions and 641 deletions

View file

@ -4,7 +4,7 @@ import (
"context"
"net/http"
"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/Gleipnir-Technology/nidus-sync/platform"
@ -16,7 +16,6 @@ type contentCell struct {
BreedingSources []platform.BreedingSourceSummary
CellBoundary h3.CellBoundary
Inspections []platform.Inspection
MapData ComponentMap
Traps []platform.TrapSummary
Treatments []platform.Treatment
}
@ -30,10 +29,6 @@ func getCellDetails(ctx context.Context, r *http.Request, user platform.User) (*
if err != nil {
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Cannot convert provided cell to uint64")
}
center, err := h3.Cell(c).LatLng()
if err != nil {
return nil, nhttp.NewError("Failed to get center: %w", err)
}
boundary, err := h3.Cell(c).Boundary()
if err != nil {
return nil, nhttp.NewError("Failed to get boundary: %w", err)
@ -42,11 +37,17 @@ func getCellDetails(ctx context.Context, r *http.Request, user platform.User) (*
if err != nil {
return nil, nhttp.NewError("Failed to get inspections by cell: %w", err)
}
/*
center, err := h3.Cell(c).LatLng()
if err != nil {
return nil, nhttp.NewError("Failed to get center: %w", err)
}
geojson, err := h3utils.H3ToGeoJSON([]h3.Cell{h3.Cell(c)})
if err != nil {
return nil, nhttp.NewError("Failed to get boundaries: %w", err)
}
resolution := h3.Cell(c).Resolution()
*/
sources, err := platform.BreedingSourcesByCell(ctx, user.Organization, h3.Cell(c))
if err != nil {
return nil, nhttp.NewError("Failed to get sources: %w", err)
@ -64,14 +65,6 @@ func getCellDetails(ctx context.Context, r *http.Request, user platform.User) (*
BreedingSources: sources,
CellBoundary: boundary,
Inspections: inspections,
MapData: ComponentMap{
Center: h3.LatLng{
Lat: center.Lat,
Lng: center.Lng,
},
GeoJSON: geojson,
Zoom: resolution + 5,
},
Traps: traps,
Treatments: treatments,
}), nil

View file

@ -1,146 +0,0 @@
package sync
import (
"context"
"net/http"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/um"
"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/Gleipnir-Technology/nidus-sync/platform"
"github.com/rs/zerolog/log"
)
type contentConfigurationRoot struct{}
func getConfigurationRoot(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentConfigurationRoot], *nhttp.ErrorWithStatus) {
return html.NewResponse("sync/configuration/root.html", contentConfigurationRoot{}), nil
}
type contentSettingOrganization struct {
Organization platform.Organization
}
type contentSettingIntegration struct {
ArcGISAccount *models.ArcgisAccount
ArcGISOAuth *models.ArcgisOauthToken
ServiceMaps []*models.ArcgisServiceMap
}
func getConfigurationOrganization(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentSettingOrganization], *nhttp.ErrorWithStatus) {
/*
var district contentDistrict
district, err = bob.One[contentDistrict](ctx, db.PGInstance.BobDB, psql.Select(
sm.From("import.district"),
sm.Columns(
"address",
"agency",
"area_4326_sqm",
"city1",
"city2",
"contact",
"fax1",
"general_mg",
"gid",
"phone1",
"phone2",
"postal_c_1",
"website",
psql.F("ST_AsGeoJSON", "centroid_4326"),
psql.F("ST_XMin", "extent_4326"),
psql.F("ST_YMin", "extent_4326"),
psql.F("ST_XMax", "extent_4326"),
psql.F("ST_YMax", "extent_4326"),
),
sm.Where(psql.Quote("gid").EQ(psql.Arg(gid))),
), scan.StructMapper[contentDistrict]())
if err != nil {
respondError(w, "Failed to get extents", err, http.StatusInternalServerError)
return
}
*/
data := contentSettingOrganization{
Organization: u.Organization,
}
return html.NewResponse("sync/configuration/organization.html", data), nil
}
func getConfigurationIntegration(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentSettingIntegration], *nhttp.ErrorWithStatus) {
oauth, err := platform.GetOAuthForUser(ctx, u)
if err != nil {
return nil, nhttp.NewError("Failed to get oauth: %w", err)
}
data := contentSettingIntegration{
ArcGISOAuth: oauth,
}
return html.NewResponse("sync/configuration/integration.html", data), nil
}
func getConfigurationIntegrationArcgis(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentSettingIntegration], *nhttp.ErrorWithStatus) {
oauth, err := platform.GetOAuthForUser(ctx, u)
if err != nil {
return nil, nhttp.NewError("Failed to get oauth: %w", err)
}
var account *models.ArcgisAccount
var service_maps []*models.ArcgisServiceMap
account_id := u.Organization.ArcgisAccountID()
if account_id != "" {
account, err = models.FindArcgisAccount(ctx, db.PGInstance.BobDB, account_id)
if err != nil {
return nil, nhttp.NewError("Failed to get arcgis: %w", err)
}
service_maps, err = models.ArcgisServiceMaps.Query(
models.SelectWhere.ArcgisServiceMaps.AccountID.EQ(account.ID),
).All(ctx, db.PGInstance.BobDB)
if err != nil {
return nil, nhttp.NewError("Failed to get map services: %w", err)
}
}
data := contentSettingIntegration{
ArcGISAccount: account,
ArcGISOAuth: oauth,
ServiceMaps: service_maps,
}
return html.NewResponse("sync/configuration/integration-arcgis.html", data), nil
}
type contentSettingPlaceholder struct{}
func getConfigurationPesticide(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{}
return html.NewResponse("sync/configuration/pesticide.html", content), nil
}
func getConfigurationPesticideAdd(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{}
return html.NewResponse("sync/configuration/pesticide-add.html", content), nil
}
func getConfigurationUserAdd(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{}
return html.NewResponse("sync/configuration/user-add.html", content), nil
}
func getConfigurationUserList(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSettingPlaceholder], *nhttp.ErrorWithStatus) {
content := contentSettingPlaceholder{}
return html.NewResponse("sync/configuration/user-list.html", content), nil
}
type formArcgisConfiguration struct {
MapService *string `schema:"map-service"`
}
func postConfigurationIntegrationArcgis(ctx context.Context, r *http.Request, u platform.User, f formArcgisConfiguration) (string, *nhttp.ErrorWithStatus) {
if f.MapService != nil {
_, err := psql.Update(
um.Table("organization"),
um.SetCol("arcgis_map_service_id").ToArg(f.MapService),
um.Where(psql.Quote("id").EQ(psql.Arg(u.Organization.ID))),
).Exec(ctx, db.PGInstance.BobDB)
if err != nil {
return "", nhttp.NewError("Failed to update map service config: %w", err)
}
log.Info().Str("map-service", *f.MapService).Int32("org-id", u.Organization.ID).Msg("changed map service")
} else {
log.Info().Msg("no map service")
}
return "/configuration/integration/arcgis", nil
}

View file

@ -2,9 +2,7 @@ package sync
import (
"context"
"html/template"
"net/http"
"time"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
@ -15,7 +13,6 @@ import (
type contentSource struct {
Inspections []platform.Inspection
MapData ComponentMap
Source *platform.BreedingSourceDetail
Traps []platform.TrapNearby
Treatments []platform.Treatment
@ -24,21 +21,9 @@ type contentSource struct {
User platform.User
}
type contentTrap struct {
MapData ComponentMap
Trap platform.Trap
User platform.User
}
type contentDashboard struct {
CountTraps int
CountMosquitoSources int
CountServiceRequests int
Geo template.JS
IsSyncOngoing bool
LastSync *time.Time
MapData ComponentMap
RecentRequests []ServiceRequestSummary
}
type contentLayoutTest struct {
User platform.User
}
@ -54,50 +39,8 @@ func getLayoutTest(ctx context.Context, r *http.Request, user platform.User) (*h
return html.NewResponse("sync/layout-test.html", contentLayoutTest{}), nil
}
func getRoot(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentDashboard], *nhttp.ErrorWithStatus) {
var lastSync *time.Time
sync, err := user.Organization.FieldseekerSyncLatest(ctx)
if err != nil {
return nil, nhttp.NewError("Failed to get syncs: %w", err)
} else if sync != nil {
lastSync = &sync.Created
}
is_syncing := user.Organization.IsSyncOngoing()
count_trap, err := user.Organization.CountTrap(ctx)
if err != nil {
return nil, nhttp.NewError("Failed to get trap count: %w", err)
}
count_source, err := user.Organization.CountTrap(ctx)
if err != nil {
return nil, nhttp.NewError("Failed to get source count: %w", err)
}
count_service, err := user.Organization.CountServiceRequest(ctx)
if err != nil {
return nil, nhttp.NewError("Failed to get service count: %w", err)
}
service_request_recent, err := user.Organization.ServiceRequestRecent(ctx)
if err != nil {
return nil, nhttp.NewError("Failed to get recent service: %w", err)
}
requests := make([]ServiceRequestSummary, 0)
for _, r := range service_request_recent {
requests = append(requests, ServiceRequestSummary{
Date: r.Creationdate.MustGet(),
Location: r.Reqaddr1.MustGet(),
Status: "Completed",
})
}
content := contentDashboard{
CountTraps: int(count_trap),
CountMosquitoSources: int(count_source),
CountServiceRequests: int(count_service),
IsSyncOngoing: is_syncing,
LastSync: lastSync,
MapData: ComponentMap{},
RecentRequests: requests,
}
return html.NewResponse("sync/authenticated.html", content), nil
func getRoot(w http.ResponseWriter, r *http.Request) {
html.RenderOrError(w, "static/gen/main.html", struct{}{})
}
func getSource(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSource], *nhttp.ErrorWithStatus) {
@ -127,22 +70,8 @@ func getSource(ctx context.Context, r *http.Request, user platform.User) (*html.
return nil, nhttp.NewError("Failed to get treatments: %w", err)
}
treatment_models := platform.ModelTreatment(treatments)
latlng, err := s.H3Cell.LatLng()
if err != nil {
return nil, nhttp.NewError("Failed to get latlng: %w", err)
}
data := contentSource{
Inspections: inspections,
MapData: ComponentMap{
Center: latlng,
//GeoJSON:
Markers: []MapMarker{
MapMarker{
LatLng: latlng,
},
},
Zoom: 13,
},
Source: s,
Traps: traps,
Treatments: treatments,
@ -153,12 +82,6 @@ func getSource(ctx context.Context, r *http.Request, user platform.User) (*html.
return html.NewResponse("sync/source.html", data), nil
}
func getStadia(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentDashboard], *nhttp.ErrorWithStatus) {
data := contentDashboard{
MapData: ComponentMap{},
}
return html.NewResponse("sync/stadia.html", data), nil
}
func getTemplateTest(w http.ResponseWriter, r *http.Request) {
html.RenderOrError(w, "sync/template-test.html", nil)
}
@ -175,21 +98,13 @@ func getTrap(ctx context.Context, r *http.Request, user platform.User) (*html.Re
if err != nil {
return nil, nhttp.NewError("Failed to get trap: %w", err)
}
/*
latlng, err := t.H3Cell.LatLng()
if err != nil {
return nil, nhttp.NewError("Failed to get latlng: %w", err)
}
*/
data := contentTrap{
MapData: ComponentMap{
Center: latlng,
//GeoJSON:
Markers: []MapMarker{
MapMarker{
LatLng: latlng,
},
},
Zoom: 13,
},
Trap: *t,
User: user,
}

View file

@ -1,97 +0,0 @@
package sync
import (
"context"
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/gorilla/schema"
"github.com/rs/zerolog/log"
)
var decoder = schema.NewDecoder()
type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User) (*html.Response[T], *nhttp.ErrorWithStatus)
type wrappedHandler func(http.ResponseWriter, *http.Request)
type contentAuthenticated[T any] struct {
C T
Config html.ContentConfig
Organization platform.Organization
URL html.ContentURL
User platform.User
}
// w http.ResponseWriter, r *http.Request, u platform.User) {
func authenticatedHandler[T any](f handlerFunctionGet[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
resp, e := f(ctx, r, 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: u.Organization,
URL: html.NewContentURL(),
User: u,
})
})
}
type handlerFunctionPost[T any] func(context.Context, *http.Request, platform.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 platform.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()
path, e := f(ctx, r, 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 platform.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()
path, e := f(ctx, r, u, content)
if e != nil {
http.Error(w, e.Error(), e.Status)
return
}
http.Redirect(w, r, path, http.StatusFound)
})
}

View file

@ -2,7 +2,6 @@ package sync
import (
"github.com/Gleipnir-Technology/nidus-sync/api"
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/static"
"github.com/go-chi/chi/v5"
)
@ -31,66 +30,13 @@ func Router() chi.Router {
r.Get("/privacy", getPrivacy)
r.Get("/qr-code/report/{code}", getQRCodeReport)
r.Get("/qr-code/mailer/{code}", getQRCodeMailer)
r.Get("/signin", getSignin)
r.Post("/signin", postSignin)
r.Get("/signup", getSignup)
r.Post("/signup", postSignup)
r.Get("/template-test", getTemplateTest)
// Authenticated endpoints
r.Route("/api", api.AddRoutes)
r.Method("GET", "/", authenticatedHandler(getRoot))
r.Method("GET", "/communication", authenticatedHandler(getRoot))
r.Method("GET", "/configuration", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/integration", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/integration/arcgis", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/organization", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/pesticide", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/pesticide/add", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/upload", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/upload/pool", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/upload/pool/custom", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/upload/pool/flyover", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/upload/{id}", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/user", authenticatedHandler(getRoot))
r.Method("GET", "/configuration/user/add", authenticatedHandler(getRoot))
r.Method("GET", "/intelligence", authenticatedHandler(getRoot))
r.Method("GET", "/operations", authenticatedHandler(getRoot))
r.Method("GET", "/oauth/refresh/arcgis", authenticatedHandler(getRoot))
r.Method("GET", "/planning", authenticatedHandler(getRoot))
r.Method("GET", "/review", authenticatedHandler(getRoot))
r.Method("GET", "/review/pool", authenticatedHandler(getRoot))
r.Method("GET", "/review/site", authenticatedHandler(getRoot))
r.Method("GET", "/admin", authenticatedHandler(getAdminDash))
r.Method("GET", "/cell/{cell}", authenticatedHandler(getCellDetails))
r.Method("POST", "/configuration/integration/arcgis", authenticatedHandlerPost(postConfigurationIntegrationArcgis))
r.Method("POST", "/configuration/upload/pool/flyover", authenticatedHandlerPostMultipart(postUploadPoolFlyoverCreate))
r.Method("POST", "/configuration/upload/pool/custom", authenticatedHandlerPostMultipart(postUploadPoolCustomCreate))
r.Method("POST", "/configuration/upload/{id}/commit", authenticatedHandlerPost(postUploadCommit))
r.Method("POST", "/configuration/upload/{id}/discard", authenticatedHandlerPost(postUploadDiscard))
r.Method("GET", "/download", authenticatedHandler(getDownloadList))
r.Method("GET", "/layout-test", authenticatedHandler(getLayoutTest))
r.Method("GET", "/message", authenticatedHandler(getMessageList))
r.Method("GET", "/notification", authenticatedHandler(getNotificationList))
r.Method("GET", "/parcel", authenticatedHandler(getParcel))
r.Method("GET", "/pool", authenticatedHandler(getPoolList))
r.Method("GET", "/pool/create", authenticatedHandler(getPoolCreate))
r.Method("GET", "/pool/{id}", authenticatedHandler(getPoolByID))
r.Method("GET", "/radar", authenticatedHandler(getRadar))
r.Method("GET", "/service-request", authenticatedHandler(getServiceRequestList))
r.Method("GET", "/service-request/{id}", authenticatedHandler(getServiceRequestDetail))
r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout))
r.Method("GET", "/source/{globalid}", authenticatedHandler(getSource))
r.Method("GET", "/stadia", authenticatedHandler(getStadia))
r.Method("GET", "/sudo", authenticatedHandler(getSudo))
r.Method("POST", "/sudo/email", authenticatedHandlerPost(postSudoEmail))
r.Method("POST", "/sudo/sms", authenticatedHandlerPost(postSudoSMS))
r.Method("POST", "/sudo/sse", authenticatedHandlerPost(postSudoSSE))
r.Method("GET", "/trap/{globalid}", authenticatedHandler(getTrap))
r.Method("GET", "/text/{destination}", authenticatedHandler(getTextMessages))
r.Method("GET", "/tile/gps", auth.NewEnsureAuth(getTileGPS))
r.Get("/", getRoot)
r.Get("/_/*", getRoot)
static.AddStaticRoute(r, "/static")
return r

View file

@ -3,7 +3,6 @@ package sync
import (
"errors"
"net/http"
"strings"
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/config"
@ -16,7 +15,6 @@ type contentSignin struct {
InvalidCredentials bool
Next string
}
type contentSignup struct{}
func getSignin(w http.ResponseWriter, r *http.Request) {
errorCode := r.URL.Query().Get("error")
@ -29,15 +27,6 @@ func getSignout(w http.ResponseWriter, r *http.Request, user platform.User) {
http.Redirect(w, r, "/signin", http.StatusFound)
}
func getSignup(w http.ResponseWriter, r *http.Request) {
data := contentSignup{}
html.RenderOrError(w, "sync/signup.html", contentUnauthenticated[contentSignup]{
C: data,
Config: html.NewContentConfig(),
URL: html.NewContentURL(),
})
}
func postSignin(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
respondError(w, "Could not parse form", err, http.StatusBadRequest)
@ -70,36 +59,6 @@ func postSignin(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, location, http.StatusFound)
}
func postSignup(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
respondError(w, "Could not parse form", err, http.StatusBadRequest)
return
}
username := r.FormValue("username")
name := r.FormValue("name")
password := r.FormValue("password")
terms := r.FormValue("terms")
log.Info().Str("username", username).Str("name", name).Str("password", strings.Repeat("*", len(password))).Msg("Signup")
if terms != "on" {
log.Warn().Msg("Terms not agreed")
http.Error(w, "You must agree to the terms to register", http.StatusBadRequest)
return
}
user, err := auth.SignupUser(r.Context(), username, name, password)
if err != nil {
respondError(w, "Failed to signup user", err, http.StatusInternalServerError)
return
}
auth.AddUserSession(r, user)
http.Redirect(w, r, "/", http.StatusFound)
}
type contentUnauthenticated[T any] struct {
C T
Config html.ContentConfig

View file

@ -1,104 +1 @@
package sync
import (
"context"
"fmt"
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/comms/text"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/rs/zerolog/log"
)
type contentSudo struct {
ForwardEmailRMOAddress string
ForwardEmailNidusAddress string
}
func getSudo(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSudo], *nhttp.ErrorWithStatus) {
if !user.HasRoot() {
return nil, &nhttp.ErrorWithStatus{
Message: "You have to be a root user to access this",
Status: http.StatusForbidden,
}
}
content := contentSudo{
ForwardEmailRMOAddress: config.ForwardEmailRMOAddress,
ForwardEmailNidusAddress: config.ForwardEmailNidusAddress,
}
return html.NewResponse("sync/sudo.html", content), nil
}
type FormEmail struct {
Body string `schema:"emailBody"`
From string `schema:"emailFrom"`
Subject string `schema:"emailSubject"`
To string `schema:"emailTo"`
}
func postSudoEmail(ctx context.Context, r *http.Request, u platform.User, e FormEmail) (string, *nhttp.ErrorWithStatus) {
if !u.HasRoot() {
return "", &nhttp.ErrorWithStatus{
Message: "You must have sudo powers to do this",
Status: http.StatusForbidden,
}
}
request := email.Request{
From: e.From,
HTML: fmt.Sprintf("<html><p>%s</p></html>", e.Body),
Sender: e.From,
Subject: e.Subject,
To: e.To,
Text: e.Body,
}
resp, err := email.Send(ctx, request)
if err != nil {
log.Warn().Err(err).Msg("Failed to send email")
} else {
log.Info().Str("id", resp.ID).Str("to", e.To).Msg("Sent Email")
}
return "/sudo", nil
}
type FormSMS struct {
Message string `schema:"smsMessage"`
Phone string `schema:"smsPhone"`
}
func postSudoSMS(ctx context.Context, r *http.Request, u platform.User, sms FormSMS) (string, *nhttp.ErrorWithStatus) {
if !u.HasRoot() {
return "", &nhttp.ErrorWithStatus{
Message: "You must have sudo powers to do this",
Status: http.StatusForbidden,
}
}
id, err := text.SendText(ctx, config.VoipMSNumber, sms.Phone, sms.Message)
if err != nil {
log.Warn().Err(err).Msg("Failed to send SMS")
} else {
log.Info().Str("id", id).Msg("Sent SMS")
}
return "/sudo", nil
}
type FormSSE struct {
OrganizationID int32 `schema:"organizationID"`
Resource string `schema:"resource"`
Type string `schema:"type"`
URIPath string `schema:"uriPath"`
}
func postSudoSSE(ctx context.Context, r *http.Request, u platform.User, sse FormSSE) (string, *nhttp.ErrorWithStatus) {
if !u.HasRoot() {
return "", &nhttp.ErrorWithStatus{
Message: "You must have sudo powers to do this",
Status: http.StatusForbidden,
}
}
platform.SudoEvent(sse.OrganizationID, sse.Resource, sse.Type, sse.URIPath)
return "/sudo", nil
}

View file

@ -1,20 +1,12 @@
package sync
import (
"time"
"github.com/uber/h3-go/v4"
)
type MapMarker struct {
LatLng h3.LatLng
}
type ComponentMap struct {
Center h3.LatLng
GeoJSON interface{}
Markers []MapMarker
Zoom int
}
type ContentMockURLs struct {
Dispatch string
DispatchResults string
@ -43,8 +35,3 @@ type Link struct {
Href string
Title string
}
type ServiceRequestSummary struct {
Date time.Time
Location string
Status string
}

View file

@ -1,131 +1,4 @@
package sync
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"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/file"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
)
type contentUploadList struct {
RecentUploads []platform.UploadSummary
}
type contentUploadPlaceholder struct{}
func getUploadList(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentUploadList], *nhttp.ErrorWithStatus) {
rows, err := platform.UploadSummaryList(ctx, user.Organization)
return html.NewResponse("sync/upload-list.html", contentUploadList{
RecentUploads: rows,
}), nhttp.NewErrorMaybe("get upload list: %w", err)
}
type contentUploadDetail struct {
CSVFileID int32
Organization platform.Organization
Upload platform.UploadPoolDetail
}
type contentUploadPoolList struct {
Uploads []platform.Upload
}
type contentUploadPool struct{}
func getUploadPool(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentUploadPool], *nhttp.ErrorWithStatus) {
data := contentUploadPool{}
return html.NewResponse("sync/upload-csv-pool.html", data), nil
}
type contentUploadPoolFlyoverCreate struct{}
func getUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentUploadPoolFlyoverCreate], *nhttp.ErrorWithStatus) {
data := contentUploadPoolFlyoverCreate{}
return html.NewResponse("sync/upload-csv-pool-flyover.html", data), nil
}
type contentUploadPoolCustomCreate struct{}
func getUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentUploadPoolCustomCreate], *nhttp.ErrorWithStatus) {
data := contentUploadPoolCustomCreate{}
return html.NewResponse("sync/upload-csv-pool-custom.html", data), nil
}
type FormUploadCommit struct{}
func postUploadCommit(ctx context.Context, r *http.Request, u platform.User, f FormUploadCommit) (string, *nhttp.ErrorWithStatus) {
file_id_str := chi.URLParam(r, "id")
file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil {
return "", nhttp.NewError("Failed to parse file_id: %w", err)
}
err = platform.UploadCommit(ctx, u.Organization, int32(file_id_), u)
if err != nil {
return "", nhttp.NewError("Failed to mark committed: %w", err)
}
log.Debug().Int64("file_id", file_id_).Int("user_id", u.ID).Msg("Committed file")
return "/configuration/upload", nil
}
type FormUploadDiscard struct{}
func postUploadDiscard(ctx context.Context, r *http.Request, u platform.User, f FormUploadDiscard) (string, *nhttp.ErrorWithStatus) {
file_id_str := chi.URLParam(r, "id")
file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil {
return "", nhttp.NewError("Failed to parse file_id: %w", err)
}
err = platform.UploadDiscard(ctx, u.Organization, int32(file_id_))
if err != nil {
return "", nhttp.NewError("Failed to mark discarded: %w", err)
}
return "/configuration/upload", nil
}
type FormUploadPool struct{}
func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) {
// If the organization we're uploading to doesn't have a service area, we can't process the upload correctly
if !(u.Organization.HasServiceArea() || u.Organization.IsCatchall()) {
return "", nhttp.NewErrorStatus(http.StatusConflict, "Your organization does not yet have a service area")
}
uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV)
if err != nil {
return "", nhttp.NewError("Failed to extract image uploads: %s", err)
}
if len(uploads) == 0 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found")
}
if len(uploads) != 1 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
}
upload := uploads[0]
saved_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypeFlyover)
if err != nil {
return "", nhttp.NewError("Failed to create new pool: %w", err)
}
return fmt.Sprintf("/configuration/upload/%d", saved_upload.ID), nil
}
func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) {
uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV)
if err != nil {
return "", nhttp.NewError("Failed to extract image uploads: %s", err)
}
if len(uploads) == 0 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found")
}
if len(uploads) != 1 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
}
upload := uploads[0]
pool_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypePoollist)
if err != nil {
return "", nhttp.NewError("Failed to create new pool: %w", err)
}
return fmt.Sprintf("/configuration/upload/%d", pool_upload.ID), nil
}