package sync import ( "embed" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" //"bytes" "context" //"errors" "fmt" //"html/template" //"io" //"math" "net/http" //"os" //"strconv" //"strings" "time" "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/aarondl/opt/null" "github.com/google/uuid" "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/dialect/psql/sm" "github.com/uber/h3-go/v4" ) //go:embed template/* var embeddedFiles embed.FS // Authenticated pages var ( cell = buildTemplate("cell", "authenticated") dashboard = buildTemplate("dashboard", "authenticated") oauthPrompt = buildTemplate("oauth-prompt", "authenticated") settings = buildTemplate("settings", "authenticated") source = buildTemplate("source", "authenticated") ) // Unauthenticated pages var ( admin = buildTemplate("admin", "base") dataEntry = buildTemplate("data-entry", "base") dataEntryGood = buildTemplate("data-entry-good", "base") dataEntryBad = buildTemplate("data-entry-bad", "base") dispatch = buildTemplate("dispatch", "base") dispatchResults = buildTemplate("dispatch-results", "base") mockRoot = buildTemplate("mock-root", "base") reportPage = buildTemplate("report", "base") reportConfirmation = buildTemplate("report-confirmation", "base") reportContribute = buildTemplate("report-contribute", "base") reportDetail = buildTemplate("report-detail", "base") reportEvidence = buildTemplate("report-evidence", "base") reportSchedule = buildTemplate("report-schedule", "base") reportUpdate = buildTemplate("report-update", "base") serviceRequest = buildTemplate("service-request", "base") serviceRequestDetail = buildTemplate("service-request-detail", "base") serviceRequestLocation = buildTemplate("service-request-location", "base") serviceRequestMosquito = buildTemplate("service-request-mosquito", "base") serviceRequestPool = buildTemplate("service-request-pool", "base") serviceRequestQuick = buildTemplate("service-request-quick", "base") serviceRequestQuickConfirmation = buildTemplate("service-request-quick-confirmation", "base") serviceRequestUpdates = buildTemplate("service-request-updates", "base") settingRoot = buildTemplate("setting-mock", "base") settingIntegration = buildTemplate("setting-integration", "base") settingPesticide = buildTemplate("setting-pesticide", "base") settingPesticideAdd = buildTemplate("setting-pesticide-add", "base") settingUsers = buildTemplate("setting-user", "base") settingUsersAdd = buildTemplate("setting-user-add", "base") signin = buildTemplate("signin", "base") signup = buildTemplate("signup", "base") ) var components = [...]string{"header", "map"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { subdir := "htmlpage/sync" full_files := make([]string, 0) for _, f := range files { full_files = append(full_files, fmt.Sprintf("%s/template/%s.html", subdir, f)) } for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/components/%s.html", subdir, c)) } return htmlpage.NewBuiltTemplate(embeddedFiles, "htmlpage/sync/", full_files...) } 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, cell, &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, dashboard, data) } func Mock(t string, w http.ResponseWriter, code string) { data := ContentMock{ DistrictName: "Delta MVCD", URLs: ContentMockURLs{ Dispatch: "/mock/dispatch", DispatchResults: "/mock/dispatch-results", ReportConfirmation: fmt.Sprintf("/mock/report/%s/confirm", code), ReportDetail: fmt.Sprintf("/mock/report/%s", code), ReportContribute: fmt.Sprintf("/mock/report/%s/contribute", code), ReportEvidence: fmt.Sprintf("/mock/report/%s/evidence", code), ReportSchedule: fmt.Sprintf("/mock/report/%s/schedule", code), ReportUpdate: fmt.Sprintf("/mock/report/%s/update", code), Root: "/mock", Setting: "/mock/setting", SettingIntegration: "/mock/setting/integration", SettingPesticide: "/mock/setting/pesticide", SettingPesticideAdd: "/mock/setting/pesticide/add", SettingUser: "/mock/setting/user", SettingUserAdd: "/mock/setting/user/add", }, } template, ok := htmlpage.TemplatesByFilename[t] if !ok { log.Error().Str("template", t).Msg("Failed to find template") respondError(w, "Failed to render template", nil, http.StatusInternalServerError) return } htmlpage.RenderOrError(w, &template, data) } func OauthPrompt(w http.ResponseWriter, user *models.User) { dp := user.DisplayName data := ContentDashboard{ User: User{ DisplayName: dp, Initials: extractInitials(dp), Username: user.Username, }, } htmlpage.RenderOrError(w, oauthPrompt, 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, settings, data) } func Signin(w http.ResponseWriter, errorCode string) { data := ContentSignin{ InvalidCredentials: errorCode == "invalid-credentials", } htmlpage.RenderOrError(w, signin, data) } func Signup(w http.ResponseWriter, path string) { data := ContentSignup{} htmlpage.RenderOrError(w, signup, 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, source, data) } // Respond with an error that is visible to the user 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 from sync pages") http.Error(w, m, s) }