diff --git a/arcgis.go b/arcgis.go index 0d9d5003..111b0aae 100644 --- a/arcgis.go +++ b/arcgis.go @@ -39,6 +39,8 @@ import ( "github.com/uber/h3-go/v4" ) +var syncStatusByOrg map[int32]bool + // When the API responds that the token is now invalidated type InvalidatedTokenError struct{} @@ -129,6 +131,10 @@ func generateCodeVerifier() string { return base64.RawURLEncoding.EncodeToString(bytes) } +func isSyncOngoing(org_id int32) bool { + return syncStatusByOrg[org_id] +} + // Find out what we can about this user func updateArcgisUserData(ctx context.Context, user *models.User, access_token string, access_token_expires time.Time, refresh_token string, refresh_token_expires time.Time) { client := arcgis.NewArcGIS( @@ -151,40 +157,13 @@ func updateArcgisUserData(ctx context.Context, user *models.User, access_token s log.Error().Err(err).Msg("Failed to update oauth token portal data") return } - var org *models.Organization - orgs, err := models.Organizations.Query(models.SelectWhere.Organizations.ArcgisName.EQ(portal.Name)).All(ctx, db.PGInstance.BobDB) - switch len(orgs) { - case 0: - setter := models.OrganizationSetter{ - Name: omitnull.From(portal.Name), - ArcgisID: omitnull.From(portal.User.OrgID), - ArcgisName: omitnull.From(portal.Name), - } - org, err = models.Organizations.Insert(&setter).One(ctx, db.PGInstance.BobDB) - if err != nil { - log.Error().Err(err).Msg("Failed to create new organization") - return - } - log.Info().Int("org_id", int(org.ID)).Msg("Created new organization") - case 1: - org = orgs[0] - log.Info().Msg("Organization already exists") - default: - log.Error().Msg("Got too many organizations, bailing") - return - - } + org := user.R.Organization + err = org.Update(ctx, db.PGInstance.BobDB, &models.OrganizationSetter{ + ArcgisID: omitnull.From(portal.User.OrgID), + ArcgisName: omitnull.From(portal.Name), + }) if err != nil { - debug.LogErrorTypeInfo(err) - if errors.Is(err, pgx.ErrNoRows) { - } else { - log.Error().Err(err).Msg("Failed to query for existing org") - return - } - } - err = org.AttachUser(ctx, db.PGInstance.BobDB, user) - if err != nil { - log.Error().Err(err).Int("user_id", int(user.ID)).Int("org_id", int(org.ID)).Msg("Failed to attach user to organization") + log.Error().Err(err).Int32("id", user.R.Organization.ID).Msg("Failed to update organization's arcgis info") return } @@ -292,6 +271,7 @@ func redirectURL() string { // This is a goroutine that is in charge of getting Fieldseeker data and keeping it fresh. func refreshFieldseekerData(ctx context.Context, newOauthCh <-chan struct{}) { + syncStatusByOrg = make(map[int32]bool, 0) for { workerCtx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup @@ -448,6 +428,7 @@ func periodicallyExportFieldseeker(ctx context.Context, org *models.Organization return fmt.Errorf("Failed to get oauth for org: %w", err) } err = exportFieldseekerData(ctx, org, oauth) + syncStatusByOrg[org.ID] = false if err != nil { return fmt.Errorf("Failed to export Fieldseeker data: %w", err) } @@ -458,6 +439,7 @@ func periodicallyExportFieldseeker(ctx context.Context, org *models.Organization } func exportFieldseekerData(ctx context.Context, org *models.Organization, oauth *models.OauthToken) error { log.Info().Msg("Update Fieldseeker data") + syncStatusByOrg[org.ID] = true var err error ar := arcgis.NewArcGIS( arcgis.AuthenticatorOAuth{ diff --git a/html.go b/html.go index 562907de..c978fed4 100644 --- a/html.go +++ b/html.go @@ -32,12 +32,11 @@ var embeddedFiles embed.FS // Authenticated pages var ( - cell = newBuiltTemplate("cell", "authenticated") - dashboard = newBuiltTemplate("dashboard", "authenticated") - dashboardLoading = newBuiltTemplate("dashboard-loading", "authenticated") - oauthPrompt = newBuiltTemplate("oauth-prompt", "authenticated") - settings = newBuiltTemplate("settings", "authenticated") - source = newBuiltTemplate("source", "authenticated") + cell = newBuiltTemplate("cell", "authenticated") + dashboard = newBuiltTemplate("dashboard", "authenticated") + oauthPrompt = newBuiltTemplate("oauth-prompt", "authenticated") + settings = newBuiltTemplate("settings", "authenticated") + source = newBuiltTemplate("source", "authenticated") ) // Unauthenticated pages @@ -142,6 +141,7 @@ type ContentDashboard struct { CountMosquitoSources int CountServiceRequests int Geo template.JS + IsSyncOngoing bool LastSync *time.Time MapData ComponentMap Org string @@ -315,10 +315,6 @@ func htmlCell(ctx context.Context, w http.ResponseWriter, user *models.User, c i func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { org, err := user.Organization().One(ctx, db.PGInstance.BobDB) if err != nil { - if err.Error() == "sql: no rows in result set" { - htmlDashboardLoading(ctx, w, user) - return - } respondError(w, "Failed to get org", err, http.StatusInternalServerError) return } @@ -332,6 +328,7 @@ func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User } else { lastSync = &sync.Created } + is_syncing := 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) @@ -370,6 +367,7 @@ func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User CountInspections: int(inspectionCount), CountMosquitoSources: int(sourceCount), CountServiceRequests: int(serviceCount), + IsSyncOngoing: is_syncing, LastSync: lastSync, MapData: ComponentMap{ MapboxToken: MapboxToken, @@ -381,19 +379,6 @@ func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User renderOrError(w, dashboard, data) } -// A dashboard to show while we are still downloading information about their organization -func htmlDashboardLoading(ctx context.Context, w http.ResponseWriter, user *models.User) { - userContent, err := contentForUser(ctx, user) - if err != nil { - respondError(w, "Failed to get user context", err, http.StatusInternalServerError) - return - } - data := ContentDashboardLoading{ - User: userContent, - } - renderOrError(w, dashboardLoading, data) -} - func htmlMock(t string, w http.ResponseWriter, code string) { data := ContentMock{ DistrictName: "Delta MVCD", diff --git a/templates/dashboard-loading.html b/templates/dashboard-loading.html deleted file mode 100644 index e49fca92..00000000 --- a/templates/dashboard-loading.html +++ /dev/null @@ -1,26 +0,0 @@ -{{template "authenticated.html" .}} - -{{define "title"}}Dash{{end}} -{{define "extraheader"}} - - - - -{{end}} -{{define "content"}} -
-

We're downloading the data we need, hold on. This page will refresh every 10 seconds automatically until we've got it.

-
-{{end}} diff --git a/templates/dashboard.html b/templates/dashboard.html index 6535cb77..d43918e8 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -163,6 +163,10 @@ body { font-size: 2rem; font-weight: bold; } +.syncing { + color: #28a745; + animation: fa-spin 2s linear infinite; +} {{end}} {{define "content"}} @@ -174,10 +178,16 @@ body {

Overview of mosquito control activities in your district

-

- Last updated: {{ .LastSync | timeSince }} - -

+ {{ if .IsSyncOngoing }} +

+ Syncing now... +

+ {{ else }} +

+ Last updated: {{ .LastSync | timeSince }} + +

+ {{ end }}