I like this layout makes it easier to track what functions do what and keeps templates near their render functions.
314 lines
9.1 KiB
Go
314 lines
9.1 KiB
Go
package sync
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
|
"github.com/Gleipnir-Technology/nidus-sync/background"
|
|
"github.com/Gleipnir-Technology/nidus-sync/config"
|
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
|
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
|
|
"github.com/Gleipnir-Technology/nidus-sync/htmlpage"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
"github.com/stephenafamo/bob/dialect/psql/sm"
|
|
"github.com/uber/h3-go/v4"
|
|
)
|
|
|
|
// Authenticated pages
|
|
var (
|
|
cellT = buildTemplate("cell", "authenticated")
|
|
dashboardT = buildTemplate("dashboard", "authenticated")
|
|
districtT = buildTemplate("district", "base")
|
|
settingsT = buildTemplate("settings", "authenticated")
|
|
sourceT = buildTemplate("source", "authenticated")
|
|
)
|
|
|
|
type ContextDistrict struct {
|
|
MapboxToken string
|
|
}
|
|
|
|
func getCellDetails(w http.ResponseWriter, r *http.Request, user *models.User) {
|
|
cell_str := chi.URLParam(r, "cell")
|
|
if cell_str == "" {
|
|
respondError(w, "There should always be a cell", nil, http.StatusBadRequest)
|
|
return
|
|
}
|
|
c, err := HexToInt64(cell_str)
|
|
if err != nil {
|
|
respondError(w, "Cannot convert provided cell to uint64", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
cell(r.Context(), w, user, c)
|
|
}
|
|
|
|
func getDistrict(w http.ResponseWriter, r *http.Request) {
|
|
context := ContextDistrict{
|
|
MapboxToken: config.MapboxToken,
|
|
}
|
|
htmlpage.RenderOrError(w, districtT, &context)
|
|
}
|
|
|
|
func getFavicon(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-type", "image/x-icon")
|
|
|
|
http.ServeFile(w, r, "static/favicon.ico")
|
|
}
|
|
|
|
func getRoot(w http.ResponseWriter, r *http.Request) {
|
|
user, err := auth.GetAuthenticatedUser(r)
|
|
if err != nil {
|
|
// No credentials or user not found: go to login
|
|
if errors.Is(err, &auth.NoCredentialsError{}) || errors.Is(err, &auth.NoUserError{}) {
|
|
http.Redirect(w, r, "/signin", http.StatusFound)
|
|
return
|
|
} else {
|
|
respondError(w, "Failed to get root", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
if user == nil {
|
|
errorCode := r.URL.Query().Get("error")
|
|
signin(w, errorCode)
|
|
return
|
|
} else {
|
|
has, err := background.HasFieldseekerConnection(r.Context(), user)
|
|
if err != nil {
|
|
respondError(w, "Failed to check for ArcGIS connection", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if has {
|
|
dashboard(r.Context(), w, user)
|
|
return
|
|
} else {
|
|
oauthPrompt(w, user)
|
|
return
|
|
}
|
|
}
|
|
if err != nil {
|
|
respondError(w, "Failed to render root", err, http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func getSettings(w http.ResponseWriter, r *http.Request, u *models.User) {
|
|
settings(w, r, u)
|
|
}
|
|
|
|
func getSource(w http.ResponseWriter, r *http.Request, u *models.User) {
|
|
globalid_s := chi.URLParam(r, "globalid")
|
|
if globalid_s == "" {
|
|
respondError(w, "No globalid provided", nil, http.StatusBadRequest)
|
|
return
|
|
}
|
|
globalid, err := uuid.Parse(globalid_s)
|
|
if err != nil {
|
|
respondError(w, "globalid is not a UUID", nil, http.StatusBadRequest)
|
|
return
|
|
}
|
|
source(w, r, u, globalid)
|
|
}
|
|
|
|
func cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64) {
|
|
org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
userContent, err := contentForUser(ctx, user)
|
|
if err != nil {
|
|
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
center, err := h3.Cell(c).LatLng()
|
|
if err != nil {
|
|
respondError(w, "Failed to get center", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
boundary, err := h3.Cell(c).Boundary()
|
|
if err != nil {
|
|
respondError(w, "Failed to get boundary", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
inspections, err := inspectionsByCell(ctx, org, h3.Cell(c))
|
|
if err != nil {
|
|
respondError(w, "Failed to get inspections by cell", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
geojson, err := h3utils.H3ToGeoJSON([]h3.Cell{h3.Cell(c)})
|
|
if err != nil {
|
|
respondError(w, "Failed to get boundaries", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
resolution := h3.Cell(c).Resolution()
|
|
sources, err := breedingSourcesByCell(ctx, org, h3.Cell(c))
|
|
if err != nil {
|
|
respondError(w, "Failed to get sources", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
treatments, err := treatmentsByCell(ctx, org, h3.Cell(c))
|
|
if err != nil {
|
|
respondError(w, "Failed to get treatments", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data := ContentCell{
|
|
BreedingSources: sources,
|
|
CellBoundary: boundary,
|
|
Inspections: inspections,
|
|
MapData: ComponentMap{
|
|
Center: h3.LatLng{
|
|
Lat: center.Lat,
|
|
Lng: center.Lng,
|
|
},
|
|
GeoJSON: geojson,
|
|
MapboxToken: config.MapboxToken,
|
|
Zoom: resolution + 5,
|
|
},
|
|
Treatments: treatments,
|
|
User: userContent,
|
|
}
|
|
htmlpage.RenderOrError(w, cellT, &data)
|
|
}
|
|
|
|
func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) {
|
|
org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
var lastSync *time.Time
|
|
sync, err := org.FieldseekerSyncs(sm.OrderBy("created").Desc()).One(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
if err.Error() != "sql: no rows in result set" {
|
|
respondError(w, "Failed to get syncs", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
} else {
|
|
lastSync = &sync.Created
|
|
}
|
|
is_syncing := background.IsSyncOngoing(org.ID)
|
|
inspectionCount, err := org.Mosquitoinspections().Count(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get inspection count", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
sourceCount, err := org.Pointlocations().Count(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get source count", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
serviceCount, err := org.Servicerequests().Count(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get service count", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
recentRequests, err := org.Servicerequests(sm.OrderBy("creationdate").Desc(), sm.Limit(10)).All(ctx, db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get recent service", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
requests := make([]ServiceRequestSummary, 0)
|
|
for _, r := range recentRequests {
|
|
requests = append(requests, ServiceRequestSummary{
|
|
Date: r.Creationdate.MustGet(),
|
|
Location: r.Reqaddr1.MustGet(),
|
|
Status: "Completed",
|
|
})
|
|
}
|
|
userContent, err := contentForUser(ctx, user)
|
|
if err != nil {
|
|
respondError(w, "Failed to get user context", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data := ContentDashboard{
|
|
CountInspections: int(inspectionCount),
|
|
CountMosquitoSources: int(sourceCount),
|
|
CountServiceRequests: int(serviceCount),
|
|
IsSyncOngoing: is_syncing,
|
|
LastSync: lastSync,
|
|
MapData: ComponentMap{
|
|
MapboxToken: config.MapboxToken,
|
|
},
|
|
Org: org.Name.MustGet(),
|
|
RecentRequests: requests,
|
|
User: userContent,
|
|
}
|
|
htmlpage.RenderOrError(w, dashboardT, data)
|
|
}
|
|
|
|
func settings(w http.ResponseWriter, r *http.Request, user *models.User) {
|
|
userContent, err := contentForUser(r.Context(), user)
|
|
if err != nil {
|
|
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data := ContentAuthenticatedPlaceholder{
|
|
User: userContent,
|
|
}
|
|
htmlpage.RenderOrError(w, settingsT, data)
|
|
}
|
|
|
|
func source(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.UUID) {
|
|
org, err := user.Organization().One(r.Context(), db.PGInstance.BobDB)
|
|
if err != nil {
|
|
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
userContent, err := contentForUser(r.Context(), user)
|
|
if err != nil {
|
|
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
s, err := sourceByGlobalId(r.Context(), org, id)
|
|
if err != nil {
|
|
respondError(w, "Failed to get source", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
inspections, err := inspectionsBySource(r.Context(), org, id)
|
|
if err != nil {
|
|
respondError(w, "Failed to get inspections", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
traps, err := trapsBySource(r.Context(), org, id)
|
|
if err != nil {
|
|
respondError(w, "Failed to get traps", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
treatments, err := treatmentsBySource(r.Context(), org, id)
|
|
if err != nil {
|
|
respondError(w, "Failed to get treatments", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
treatment_models := modelTreatment(treatments)
|
|
latlng, err := s.H3Cell.LatLng()
|
|
if err != nil {
|
|
respondError(w, "Failed to get latlng", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data := ContentSource{
|
|
Inspections: inspections,
|
|
MapData: ComponentMap{
|
|
Center: latlng,
|
|
//GeoJSON:
|
|
MapboxToken: config.MapboxToken,
|
|
Markers: []MapMarker{
|
|
MapMarker{
|
|
LatLng: latlng,
|
|
},
|
|
},
|
|
Zoom: 13,
|
|
},
|
|
Source: s,
|
|
Traps: traps,
|
|
Treatments: treatments,
|
|
TreatmentModels: treatment_models,
|
|
User: userContent,
|
|
}
|
|
|
|
htmlpage.RenderOrError(w, sourceT, data)
|
|
}
|