Move all sync pages to authenticatedHandler
Still need to fix many templates
This commit is contained in:
parent
85d2d0b95b
commit
dac52a879a
23 changed files with 409 additions and 476 deletions
|
|
@ -51,7 +51,7 @@ func (ts templateSystemDisk) renderOrError(w http.ResponseWriter, template_name
|
|||
buf := &bytes.Buffer{}
|
||||
err = t.Execute(buf, context)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to render template")
|
||||
log.Error().Err(err).Str("name", template_name).Msg("Failed to render template")
|
||||
RespondError(w, "Failed to render template", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<div
|
||||
class="col-md-6 text-md-end d-flex align-items-center justify-content-md-end"
|
||||
>
|
||||
{{ if .IsSyncOngoing }}
|
||||
{{ if .C.IsSyncOngoing }}
|
||||
<p class="last-refreshed mb-0">
|
||||
<i class="fas fa-sync-alt me-2 syncing"></i>Syncing now...
|
||||
</p>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<p class="last-refreshed mb-0">
|
||||
<i class="fas fa-sync-alt me-2"></i>Last updated:
|
||||
<span id="last-refreshed-time"
|
||||
>{{ .LastSync | timeRelativePtr }}</span
|
||||
>{{ .C.LastSync | timeRelativePtr }}</span
|
||||
>
|
||||
<button class="btn btn-sm btn-outline-primary ms-3">
|
||||
Refresh Data
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Last Data Refresh</h5>
|
||||
<p class="metric-value">{{ .LastSync | timeRelativePtr }}</p>
|
||||
<p class="metric-value">{{ .C.LastSync | timeRelativePtr }}</p>
|
||||
<!-- <p class="card-text text-muted">Last sync: 12:45 PM</p> -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -71,13 +71,13 @@
|
|||
<i class="fas fa-ticket-alt"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Service Requests</h5>
|
||||
{{ if .IsSyncOngoing }}
|
||||
{{ if .C.IsSyncOngoing }}
|
||||
<p class="metric-value">
|
||||
{{ .CountServiceRequests | bigNumber }}...?
|
||||
{{ .C.CountServiceRequests | bigNumber }}...?
|
||||
</p>
|
||||
{{ else }}
|
||||
<p class="metric-value">
|
||||
{{ .CountServiceRequests | bigNumber }}
|
||||
{{ .C.CountServiceRequests | bigNumber }}
|
||||
</p>
|
||||
{{ end }}
|
||||
<!--<p class="card-text text-muted">
|
||||
|
|
@ -97,13 +97,13 @@
|
|||
<i class="fas fa-bug"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Mosquito Sources</h5>
|
||||
{{ if .IsSyncOngoing }}
|
||||
{{ if .C.IsSyncOngoing }}
|
||||
<p class="metric-value">
|
||||
{{ .CountMosquitoSources | bigNumber }}..?
|
||||
{{ .C.CountMosquitoSources | bigNumber }}..?
|
||||
</p>
|
||||
{{ else }}
|
||||
<p class="metric-value">
|
||||
{{ .CountMosquitoSources | bigNumber }}
|
||||
{{ .C.CountMosquitoSources | bigNumber }}
|
||||
</p>
|
||||
{{ end }}
|
||||
<!-- <p class="card-text text-muted">
|
||||
|
|
@ -123,10 +123,10 @@
|
|||
<i class="fas fa-clipboard-check"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Traps</h5>
|
||||
{{ if .IsSyncOngoing }}
|
||||
<p class="metric-value">{{ .CountTraps | bigNumber }}...?</p>
|
||||
{{ if .C.IsSyncOngoing }}
|
||||
<p class="metric-value">{{ .C.CountTraps | bigNumber }}...?</p>
|
||||
{{ else }}
|
||||
<p class="metric-value">{{ .CountTraps | bigNumber }}</p>
|
||||
<p class="metric-value">{{ .C.CountTraps | bigNumber }}</p>
|
||||
{{ end }}
|
||||
<!-- <p class="card-text text-muted">
|
||||
<span class="text-success">
|
||||
|
|
@ -142,17 +142,21 @@
|
|||
<h3 class="section-title">Mosquito Activity Heatmap</h3>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<map-aggregate
|
||||
centroid="{{ if .Organization.ServiceAreaCentroidGeojson.IsValue }}
|
||||
{{ .Organization.ServiceAreaCentroidGeojson.MustGet|json }}
|
||||
{{ end }}"
|
||||
organization-id="{{ .Organization.ID }}"
|
||||
tegola="{{ .URL.Tegola }}"
|
||||
xmin="{{ .Organization.ServiceAreaXmin.GetOr 0 }}"
|
||||
ymin="{{ .Organization.ServiceAreaYmin.GetOr 0 }}"
|
||||
xmax="{{ .Organization.ServiceAreaXmax.GetOr 0 }}"
|
||||
ymax="{{ .Organization.ServiceAreaYmax.GetOr 0 }}"
|
||||
/>
|
||||
{{ if not (eq .Organization nil) }}
|
||||
{{ if not (eq .Organization.ServiceAreaCentroidGeojson nil) }}
|
||||
<map-aggregate
|
||||
centroid="{{ if .Organization.ServiceAreaCentroidGeojson.IsValue }}
|
||||
{{ .Organization.ServiceAreaCentroidGeojson.MustGet|json }}
|
||||
{{ end }}"
|
||||
organization-id="{{ .Organization.ID }}"
|
||||
tegola="{{ .URL.Tegola }}"
|
||||
xmin="{{ .Organization.ServiceAreaXmin.GetOr 0 }}"
|
||||
ymin="{{ .Organization.ServiceAreaYmin.GetOr 0 }}"
|
||||
xmax="{{ .Organization.ServiceAreaXmax.GetOr 0 }}"
|
||||
ymax="{{ .Organization.ServiceAreaYmax.GetOr 0 }}"
|
||||
/>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -174,7 +178,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $i, $sr := .RecentRequests }}
|
||||
{{ range $i, $sr := .C.RecentRequests }}
|
||||
<tr>
|
||||
<td>{{ $sr.Date | timeRelativePtr }}</td>
|
||||
<td>Service Request</td>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@
|
|||
setTooltipsForSidebar();
|
||||
});
|
||||
</script>
|
||||
{{ if not .Config.IsProductionEnvironment }}
|
||||
<script src="/.flogo/injector.js"></script>
|
||||
{{ end }}
|
||||
</head>
|
||||
<body>
|
||||
{{ template "sync/component/icons.html" }}
|
||||
|
|
@ -60,6 +63,7 @@
|
|||
<div id="content">
|
||||
{{ template "content" . }}
|
||||
</div>
|
||||
<div id="flogo"></div>
|
||||
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.getElementById("sidebarToggle").addEventListener("click", () => {
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@
|
|||
<!-- favicon -->
|
||||
<link rel="icon" href="/static/favicon-sync.ico" type="image/x-icon" />
|
||||
{{ block "extraheader" . }}{{ end }}
|
||||
{{ if not .Config.IsProductionEnvironment }}
|
||||
<script src="/.flogo/injector.js"></script>
|
||||
{{ end }}
|
||||
</head>
|
||||
<body>
|
||||
{{ template "content" . }}
|
||||
<div id="flogo"></div>
|
||||
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
7
main.go
7
main.go
|
|
@ -75,7 +75,12 @@ func main() {
|
|||
defer sentryWriter.Close()
|
||||
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
log.Logger = log.Output(zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr}, sentryWriter)).Level(zerolog.InfoLevel)
|
||||
log.Logger = log.Output(zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr}, sentryWriter))
|
||||
if os.Getenv("VERBOSE") != "" {
|
||||
log.Logger = log.Logger.Level(zerolog.DebugLevel)
|
||||
} else {
|
||||
log.Logger = log.Logger.Level(zerolog.InfoLevel)
|
||||
}
|
||||
|
||||
err = db.InitializeDatabase(context.TODO(), config.PGDSN)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentAdminDash struct{}
|
||||
|
||||
func getAdminDash(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getAdminDash(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentAdminDash], *errorWithStatus) {
|
||||
content := contentAdminDash{}
|
||||
return "sync/admin-dash.html", content, nil
|
||||
return newResponse("sync/admin-dash.html", content), nil
|
||||
}
|
||||
|
|
|
|||
52
sync/cell.go
52
sync/cell.go
|
|
@ -1,12 +1,11 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"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/html"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/uber/h3-go/v4"
|
||||
)
|
||||
|
|
@ -18,70 +17,48 @@ type contentCell struct {
|
|||
MapData ComponentMap
|
||||
Traps []TrapSummary
|
||||
Treatments []Treatment
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
|
||||
func getCellDetails(w http.ResponseWriter, r *http.Request, user *models.User) {
|
||||
func getCellDetails(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentCell], *errorWithStatus) {
|
||||
cell_str := chi.URLParam(r, "cell")
|
||||
if cell_str == "" {
|
||||
respondError(w, "There should always be a cell", nil, http.StatusBadRequest)
|
||||
return
|
||||
return nil, newErrorStatus(http.StatusBadRequest, "There should always be a cell")
|
||||
}
|
||||
c, err := HexToInt64(cell_str)
|
||||
if err != nil {
|
||||
respondError(w, "Cannot convert provided cell to uint64", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
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
|
||||
return nil, newErrorStatus(http.StatusBadRequest, "Cannot convert provided cell to uint64")
|
||||
}
|
||||
center, err := h3.Cell(c).LatLng()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get center", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get center: %w", err)
|
||||
}
|
||||
boundary, err := h3.Cell(c).Boundary()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get boundary", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get boundary: %w", err)
|
||||
}
|
||||
inspections, err := inspectionsByCell(ctx, org, h3.Cell(c))
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get inspections by cell", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get inspections by cell: %w", err)
|
||||
}
|
||||
geojson, err := h3utils.H3ToGeoJSON([]h3.Cell{h3.Cell(c)})
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get boundaries", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get boundaries: %w", err)
|
||||
}
|
||||
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
|
||||
return nil, newError("Failed to get sources: %w", err)
|
||||
}
|
||||
traps, err := trapsByCell(ctx, org, h3.Cell(c))
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get traps", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get traps: %w", err)
|
||||
}
|
||||
|
||||
treatments, err := treatmentsByCell(ctx, org, h3.Cell(c))
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get treatments", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get treatments: %w", err)
|
||||
}
|
||||
data := contentCell{
|
||||
return newResponse("sync/cell.html", contentCell{
|
||||
BreedingSources: sources,
|
||||
CellBoundary: boundary,
|
||||
Inspections: inspections,
|
||||
|
|
@ -95,8 +72,5 @@ func getCellDetails(w http.ResponseWriter, r *http.Request, user *models.User) {
|
|||
},
|
||||
Traps: traps,
|
||||
Treatments: treatments,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/cell.html", &data)
|
||||
}), nil
|
||||
}
|
||||
|
|
|
|||
15
sync/config.go
Normal file
15
sync/config.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
)
|
||||
|
||||
type contentConfig struct {
|
||||
IsProductionEnvironment bool
|
||||
}
|
||||
|
||||
func newContentConfig() contentConfig {
|
||||
return contentConfig{
|
||||
IsProductionEnvironment: config.IsProductionEnvironment(),
|
||||
}
|
||||
}
|
||||
270
sync/dash.go
270
sync/dash.go
|
|
@ -21,10 +21,7 @@ import (
|
|||
// Authenticated pages
|
||||
var ()
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
|
||||
type ContentSource struct {
|
||||
type contentSource struct {
|
||||
Inspections []Inspection
|
||||
MapData ComponentMap
|
||||
Source *BreedingSourceDetail
|
||||
|
|
@ -34,12 +31,12 @@ type ContentSource struct {
|
|||
TreatmentModels []TreatmentModel
|
||||
User User
|
||||
}
|
||||
type ContentTrap struct {
|
||||
type contentTrap struct {
|
||||
MapData ComponentMap
|
||||
Trap Trap
|
||||
User User
|
||||
}
|
||||
type ContentDashboard struct {
|
||||
type contentDashboard struct {
|
||||
CountTraps int
|
||||
CountMosquitoSources int
|
||||
CountServiceRequests int
|
||||
|
|
@ -47,13 +44,10 @@ type ContentDashboard struct {
|
|||
IsSyncOngoing bool
|
||||
LastSync *time.Time
|
||||
MapData ComponentMap
|
||||
Organization *models.Organization
|
||||
RecentRequests []ServiceRequestSummary
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
|
||||
type ContentLayoutTest struct {
|
||||
type contentLayoutTest struct {
|
||||
User User
|
||||
}
|
||||
type ContentDistrict struct {
|
||||
|
|
@ -67,16 +61,12 @@ func getDistrict(w http.ResponseWriter, r *http.Request) {
|
|||
html.RenderOrError(w, "sync/district.html", &context)
|
||||
}
|
||||
|
||||
func getLayoutTest(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
html.RenderOrError(w, "sync/layout-test.html", &ContentLayoutTest{User: userContent})
|
||||
func getLayoutTest(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentLayoutTest], *errorWithStatus) {
|
||||
return newResponse("sync/layout-test.html", contentLayoutTest{}), nil
|
||||
}
|
||||
|
||||
func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user, err := auth.GetAuthenticatedUser(r)
|
||||
if err != nil {
|
||||
// No credentials or user not found: go to login
|
||||
|
|
@ -93,77 +83,135 @@ func getRoot(w http.ResponseWriter, r *http.Request) {
|
|||
signin(w, errorCode, "/")
|
||||
return
|
||||
} else {
|
||||
has, err := background.HasFieldseekerConnection(r.Context(), user)
|
||||
has, err := background.HasFieldseekerConnection(ctx, user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to check for ArcGIS connection", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if has {
|
||||
dashboard(r.Context(), w, user)
|
||||
org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get organization", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
dashboard(ctx, w, org, user)
|
||||
return
|
||||
} else {
|
||||
oauthPrompt(w, r, user)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
respondError(w, "Failed to render root", err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func getSource(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
func getSource(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSource], *errorWithStatus) {
|
||||
globalid_s := chi.URLParam(r, "globalid")
|
||||
if globalid_s == "" {
|
||||
respondError(w, "No globalid provided", nil, http.StatusBadRequest)
|
||||
return
|
||||
return nil, newError("No globalid provided: %w", nil)
|
||||
}
|
||||
globalid, err := uuid.Parse(globalid_s)
|
||||
if err != nil {
|
||||
respondError(w, "globalid is not a UUID", nil, http.StatusBadRequest)
|
||||
return
|
||||
return nil, newError("globalid is not a UUID: %w", nil)
|
||||
}
|
||||
source(w, r, u, globalid)
|
||||
userContent, err := contentForUser(r.Context(), user)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get user content: %w", err)
|
||||
}
|
||||
s, err := sourceByGlobalId(r.Context(), org, globalid)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get source: %w", err)
|
||||
}
|
||||
inspections, err := inspectionsBySource(r.Context(), org, globalid)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get inspections: %w", err)
|
||||
}
|
||||
traps, err := trapsBySource(r.Context(), org, globalid)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get traps: %w", err)
|
||||
}
|
||||
|
||||
treatments, err := treatmentsBySource(r.Context(), org, globalid)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get treatments: %w", err)
|
||||
}
|
||||
treatment_models := modelTreatment(treatments)
|
||||
latlng, err := s.H3Cell.LatLng()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get latlng: %w", err)
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
||||
return newResponse("sync/source.html", data), nil
|
||||
}
|
||||
|
||||
func getStadia(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentDashboard{
|
||||
func getStadia(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentDashboard], *errorWithStatus) {
|
||||
data := contentDashboard{
|
||||
MapData: ComponentMap{
|
||||
MapboxToken: config.MapboxToken,
|
||||
},
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/stadia.html", data)
|
||||
|
||||
return newResponse("sync/stadia.html", data), nil
|
||||
}
|
||||
func getTemplateTest(w http.ResponseWriter, r *http.Request) {
|
||||
html.RenderOrError(w, "sync/template-test.html", nil)
|
||||
}
|
||||
func getTrap(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
func getTrap(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentTrap], *errorWithStatus) {
|
||||
globalid_s := chi.URLParam(r, "globalid")
|
||||
if globalid_s == "" {
|
||||
respondError(w, "No globalid provided", nil, http.StatusBadRequest)
|
||||
return
|
||||
return nil, newError("No globalid provided: %w", nil)
|
||||
}
|
||||
globalid, err := uuid.Parse(globalid_s)
|
||||
if err != nil {
|
||||
respondError(w, "globalid is not a UUID", nil, http.StatusBadRequest)
|
||||
return
|
||||
return nil, newError("globalid is not a UUID: %w", nil)
|
||||
}
|
||||
trap(w, r, u, globalid)
|
||||
userContent, err := contentForUser(r.Context(), user)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get user content: %w", err)
|
||||
}
|
||||
t, err := trapByGlobalId(r.Context(), org, globalid)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get trap: %w", err)
|
||||
}
|
||||
latlng, err := t.H3Cell.LatLng()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get latlng: %w", err)
|
||||
}
|
||||
data := contentTrap{
|
||||
MapData: ComponentMap{
|
||||
Center: latlng,
|
||||
//GeoJSON:
|
||||
MapboxToken: config.MapboxToken,
|
||||
Markers: []MapMarker{
|
||||
MapMarker{
|
||||
LatLng: latlng,
|
||||
},
|
||||
},
|
||||
Zoom: 13,
|
||||
},
|
||||
Trap: t,
|
||||
User: userContent,
|
||||
}
|
||||
return newResponse("sync/trap.html", data), nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
func dashboard(ctx context.Context, w http.ResponseWriter, org *models.Organization, user *models.User) {
|
||||
var lastSync *time.Time
|
||||
sync, err := org.FieldseekerSyncs(sm.OrderBy("created").Desc()).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
|
|
@ -204,12 +252,7 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) {
|
|||
Status: "Completed",
|
||||
})
|
||||
}
|
||||
userContent, err := contentForUser(ctx, user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user context", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentDashboard{
|
||||
content := contentDashboard{
|
||||
CountTraps: int(trapCount),
|
||||
CountMosquitoSources: int(sourceCount),
|
||||
CountServiceRequests: int(serviceCount),
|
||||
|
|
@ -218,111 +261,22 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) {
|
|||
MapData: ComponentMap{
|
||||
MapboxToken: config.MapboxToken,
|
||||
},
|
||||
Organization: org,
|
||||
RecentRequests: requests,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/dashboard.html", 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)
|
||||
userContent, err := contentForUser(ctx, user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), 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,
|
||||
}
|
||||
|
||||
html.RenderOrError(w, "sync/source.html", data)
|
||||
}
|
||||
|
||||
func trap(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
|
||||
}
|
||||
t, err := trapByGlobalId(r.Context(), org, id)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get trap", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
latlng, err := t.H3Cell.LatLng()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get latlng", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentTrap{
|
||||
MapData: ComponentMap{
|
||||
Center: latlng,
|
||||
//GeoJSON:
|
||||
MapboxToken: config.MapboxToken,
|
||||
Markers: []MapMarker{
|
||||
MapMarker{
|
||||
LatLng: latlng,
|
||||
},
|
||||
},
|
||||
Zoom: 13,
|
||||
},
|
||||
Trap: t,
|
||||
html.RenderOrError(w, "sync/dashboard.html", contentAuthenticated[contentDashboard]{
|
||||
C: content,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
|
||||
html.RenderOrError(w, "sync/trap.html", data)
|
||||
})
|
||||
}
|
||||
|
||||
func source(w http.ResponseWriter, r *http.Request, org *models.Organization, user *models.User, id uuid.UUID) {
|
||||
}
|
||||
|
||||
func trap(w http.ResponseWriter, r *http.Request, org *models.Organization, user *models.User, id uuid.UUID) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentDownloadPlaceholder struct{}
|
||||
|
||||
func getDownloadList(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return "sync/download-list.html", content, nil
|
||||
func getDownloadList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentDownloadPlaceholder], *errorWithStatus) {
|
||||
content := contentDownloadPlaceholder{}
|
||||
return newResponse("sync/download-list.html", content), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentMessageList struct{}
|
||||
|
||||
func getMessageList(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getMessageList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentMessageList], *errorWithStatus) {
|
||||
content := contentMessageList{}
|
||||
return "sync/message-list.html", content, nil
|
||||
return newResponse("sync/message-list.html", content), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
//"context"
|
||||
"context"
|
||||
//"fmt"
|
||||
"net/http"
|
||||
//"strings"
|
||||
//"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/notification"
|
||||
//"github.com/Gleipnir-Technology/bob"
|
||||
//"github.com/Gleipnir-Technology/bob/dialect/psql"
|
||||
|
|
@ -21,22 +20,14 @@ import (
|
|||
|
||||
type contentNotificationList struct {
|
||||
Notifications []notification.Notification
|
||||
User User
|
||||
}
|
||||
|
||||
func getNotificationList(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
func getNotificationList(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentNotificationList], *errorWithStatus) {
|
||||
notifications, err := notification.ForUser(ctx, u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get notifications", err, http.StatusInternalServerError)
|
||||
return nil, newError("Failed to get notifications: %w", err)
|
||||
}
|
||||
html.RenderOrError(w, "sync/notification-list.html", &contentNotificationList{
|
||||
return newResponse("sync/notification-list.html", contentNotificationList{
|
||||
Notifications: notifications,
|
||||
User: userContent,
|
||||
})
|
||||
}), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ContextOauthPrompt struct {
|
||||
User User
|
||||
}
|
||||
type ContextOauthPrompt struct{}
|
||||
|
||||
// Build the ArcGIS authorization URL with PKCE
|
||||
func buildArcGISAuthURL(clientID string) string {
|
||||
|
|
@ -77,13 +75,6 @@ func getOAuthRefresh(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func oauthPrompt(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 := ContextOauthPrompt{
|
||||
User: userContent,
|
||||
}
|
||||
data := ContextOauthPrompt{}
|
||||
html.RenderOrError(w, "sync/oauth-prompt.html", data)
|
||||
}
|
||||
|
|
|
|||
119
sync/pool.go
119
sync/pool.go
|
|
@ -1,123 +1,20 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"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/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type contentPoolDetail struct {
|
||||
CSVFileID int32
|
||||
Organization *models.Organization
|
||||
Upload platform.UploadPoolDetail
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
type ContentPoolList struct {
|
||||
Uploads []platform.PoolUpload
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
type ContentPoolUpload struct {
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
type contentPoolList struct{}
|
||||
|
||||
func getPoolList(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
ctx := r.Context()
|
||||
userContent, err := contentForUser(ctx, u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
uploads, err := platform.PoolUploadList(ctx, u.OrganizationID)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get uploads", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentPoolList{
|
||||
Uploads: uploads,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/pool-list.html", data)
|
||||
func getPoolList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPoolList], *errorWithStatus) {
|
||||
return newResponse("sync/pool-list.html", contentPoolList{}), nil
|
||||
}
|
||||
|
||||
func getPoolUpload(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentPoolUpload{
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/pool-csv-upload.html", data)
|
||||
func getPoolCreate(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPoolList], *errorWithStatus) {
|
||||
return newResponse("sync/pool-upload.html", contentPoolList{}), nil
|
||||
}
|
||||
func getPoolUploadByID(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
ctx := r.Context()
|
||||
userContent, err := contentForUser(ctx, u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get organization", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
file_id_str := chi.URLParam(r, "id")
|
||||
file_id, err := strconv.ParseInt(file_id_str, 10, 32)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to parse file_id", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
detail, err := platform.GetUploadPoolDetail(ctx, u.OrganizationID, int32(file_id))
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get pool", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := contentPoolDetail{
|
||||
CSVFileID: int32(file_id),
|
||||
Organization: org,
|
||||
Upload: detail,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/pool-by-id.html", data)
|
||||
}
|
||||
func postPoolUpload(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
|
||||
}
|
||||
uploads, err := userfile.SaveFileUpload(r, "csvfile", userfile.CollectionCSV)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(uploads) == 0 {
|
||||
respondError(w, "No upload found", nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(uploads) != 1 {
|
||||
respondError(w, "You must only submit one file at a time", nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
upload := uploads[0]
|
||||
pool_upload, err := platform.NewPoolUpload(r.Context(), u, upload)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to create new pool", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, fmt.Sprintf("/pool/upload/%d", pool_upload.ID), http.StatusFound)
|
||||
func getPoolByID(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentPoolList], *errorWithStatus) {
|
||||
return newResponse("sync/pool-by-id.html", contentPoolList{}), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
|
|
@ -11,13 +12,13 @@ type contentRadar struct {
|
|||
Organization *models.Organization
|
||||
}
|
||||
|
||||
func getRadar(ctx context.Context, user *models.User) (string, contentRadar, *errorWithStatus) {
|
||||
func getRadar(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentRadar], *errorWithStatus) {
|
||||
org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return "", contentRadar{}, newError("get org: %w", err)
|
||||
return nil, newError("get org: %w", err)
|
||||
}
|
||||
data := contentRadar{
|
||||
Organization: org,
|
||||
}
|
||||
return "sync/radar.html", data, nil
|
||||
return newResponse("sync/radar.html", data), nil
|
||||
}
|
||||
|
|
|
|||
107
sync/routes.go
107
sync/routes.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/api"
|
||||
"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/go-chi/chi/v5"
|
||||
|
|
@ -46,34 +47,37 @@ func Router() chi.Router {
|
|||
// Authenticated endpoints
|
||||
r.Route("/api", api.AddRoutes)
|
||||
r.Method("GET", "/admin", authenticatedHandler(getAdminDash))
|
||||
r.Method("GET", "/cell/{cell}", auth.NewEnsureAuth(getCellDetails))
|
||||
r.Method("GET", "/cell/{cell}", authenticatedHandler(getCellDetails))
|
||||
r.Method("GET", "/download", authenticatedHandler(getDownloadList))
|
||||
r.Method("GET", "/layout-test", auth.NewEnsureAuth(getLayoutTest))
|
||||
r.Method("GET", "/layout-test", authenticatedHandler(getLayoutTest))
|
||||
r.Method("GET", "/message", authenticatedHandler(getMessageList))
|
||||
r.Method("GET", "/notification", auth.NewEnsureAuth(getNotificationList))
|
||||
r.Method("GET", "/pool", auth.NewEnsureAuth(getPoolList))
|
||||
r.Method("GET", "/pool/upload", auth.NewEnsureAuth(getPoolUpload))
|
||||
r.Method("GET", "/pool/upload/{id}", auth.NewEnsureAuth(getPoolUploadByID))
|
||||
r.Method("POST", "/pool/upload", auth.NewEnsureAuth(postPoolUpload))
|
||||
r.Method("GET", "/notification", authenticatedHandler(getNotificationList))
|
||||
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", "/setting", auth.NewEnsureAuth(getSetting))
|
||||
r.Method("GET", "/setting/organization", auth.NewEnsureAuth(getSettingOrganization))
|
||||
r.Method("GET", "/setting/integration", auth.NewEnsureAuth(getSettingIntegration))
|
||||
r.Method("GET", "/setting", authenticatedHandler(getSetting))
|
||||
r.Method("GET", "/setting/organization", authenticatedHandler(getSettingOrganization))
|
||||
r.Method("GET", "/setting/integration", authenticatedHandler(getSettingIntegration))
|
||||
r.Method("GET", "/setting/pesticide", authenticatedHandler(getSettingPesticide))
|
||||
r.Method("GET", "/setting/pesticide/add", authenticatedHandler(getSettingPesticideAdd))
|
||||
r.Method("GET", "/setting/user", authenticatedHandler(getSettingUserList))
|
||||
r.Method("GET", "/setting/user/add", authenticatedHandler(getSettingUserAdd))
|
||||
r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout))
|
||||
r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource))
|
||||
r.Method("GET", "/stadia", auth.NewEnsureAuth(getStadia))
|
||||
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("GET", "/trap/{globalid}", auth.NewEnsureAuth(getTrap))
|
||||
r.Method("GET", "/text/{destination}", auth.NewEnsureAuth(getTextMessages))
|
||||
r.Method("GET", "/trap/{globalid}", authenticatedHandler(getTrap))
|
||||
r.Method("GET", "/text/{destination}", authenticatedHandler(getTextMessages))
|
||||
r.Method("GET", "/upload", authenticatedHandler(getUploadList))
|
||||
r.Method("GET", "/upload/pool", authenticatedHandler(getUploadPoolList))
|
||||
r.Method("GET", "/upload/pool/create", authenticatedHandler(getUploadPoolCreate))
|
||||
r.Method("POST", "/upload/pool/create", authenticatedHandlerPostMultipart(postUploadPoolCreate))
|
||||
r.Method("GET", "/upload/pool/upload/{id}", authenticatedHandler(getUploadPoolByID))
|
||||
|
||||
html.AddStaticRoute(r, "/static")
|
||||
return r
|
||||
|
|
@ -88,19 +92,36 @@ func (e *errorWithStatus) Error() string {
|
|||
return e.Message
|
||||
}
|
||||
func newError(mesg_format string, args ...interface{}) *errorWithStatus {
|
||||
return newErrorStatus(http.StatusInternalServerError, mesg_format, args...)
|
||||
}
|
||||
func newErrorStatus(status int, mesg_format string, args ...interface{}) *errorWithStatus {
|
||||
w := fmt.Errorf(mesg_format, args...)
|
||||
return &errorWithStatus{
|
||||
Message: w.Error(),
|
||||
Status: http.StatusInternalServerError,
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
|
||||
type handlerFunctionGet[T any] func(context.Context, *models.User) (string, T, *errorWithStatus)
|
||||
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
|
||||
URL ContentURL
|
||||
User User
|
||||
C T
|
||||
Config contentConfig
|
||||
Organization *models.Organization
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
|
||||
// w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
|
|
@ -112,22 +133,32 @@ func authenticatedHandler[T any](f handlerFunctionGet[T]) http.Handler {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
template, content, e := f(ctx, u)
|
||||
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
|
||||
}
|
||||
html.RenderOrError(w, template, contentAuthenticated[T]{
|
||||
C: content,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
if org == nil {
|
||||
http.Error(w, "nil org", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
html.RenderOrError(w, resp.template, contentAuthenticated[T]{
|
||||
C: resp.content,
|
||||
Organization: org,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type handlerFunctionPost[T any] func(context.Context, *models.User, T) (string, *errorWithStatus)
|
||||
type handlerFunctionPost[T any] func(context.Context, *http.Request, *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) {
|
||||
|
|
@ -145,7 +176,31 @@ func authenticatedHandlerPost[T any](f handlerFunctionPost[T]) http.Handler {
|
|||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
path, e := f(ctx, u, content)
|
||||
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 *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()
|
||||
path, e := f(ctx, r, u, content)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), e.Status)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
|
|
@ -31,11 +32,11 @@ type contentServiceRequestList struct {
|
|||
ClosedRequests []contentClosedServiceRequest
|
||||
}
|
||||
|
||||
func getServiceRequestDetail(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getServiceRequestDetail(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentServiceRequestDetail], *errorWithStatus) {
|
||||
content := contentServiceRequestDetail{}
|
||||
return "sync/service-request-detail.html", content, nil
|
||||
return newResponse("sync/service-request-detail.html", content), nil
|
||||
}
|
||||
func getServiceRequestList(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getServiceRequestList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentServiceRequestList], *errorWithStatus) {
|
||||
now := time.Now()
|
||||
content := contentServiceRequestList{
|
||||
ActiveRequests: []contentActiveServiceRequest{
|
||||
|
|
@ -111,5 +112,5 @@ func getServiceRequestList(ctx context.Context, user *models.User) (string, inte
|
|||
},
|
||||
},
|
||||
}
|
||||
return "sync/service-request-list.html", content, nil
|
||||
return newResponse("sync/service-request-list.html", content), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,42 +7,29 @@ import (
|
|||
"github.com/Gleipnir-Technology/nidus-sync/arcgis"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type contentSettingOrganization struct {
|
||||
Organization *models.Organization
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
|
||||
type contentSettingIntegration struct {
|
||||
ArcGISOAuth *models.OauthToken
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
|
||||
func getSetting(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentAuthenticatedPlaceholder{
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/settings.html", data)
|
||||
type contentAuthenticatedPlaceholder struct {
|
||||
}
|
||||
func getSettingOrganization(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
ctx := r.Context()
|
||||
userContent, err := contentForUser(ctx, u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
func getSetting(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentAuthenticatedPlaceholder], *errorWithStatus) {
|
||||
data := contentAuthenticatedPlaceholder{}
|
||||
return newResponse("sync/settings.html", data), nil
|
||||
}
|
||||
func getSettingOrganization(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingOrganization], *errorWithStatus) {
|
||||
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, newError("get organization: %w", err)
|
||||
}
|
||||
/*
|
||||
var district contentDistrict
|
||||
district, err = bob.One[contentDistrict](ctx, db.PGInstance.BobDB, psql.Select(
|
||||
|
|
@ -76,46 +63,35 @@ func getSettingOrganization(w http.ResponseWriter, r *http.Request, u *models.Us
|
|||
*/
|
||||
data := contentSettingOrganization{
|
||||
Organization: org,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/setting-organization.html", data)
|
||||
return newResponse("sync/setting-organization.html", data), nil
|
||||
}
|
||||
func getSettingIntegration(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
ctx := r.Context()
|
||||
userContent, err := contentForUser(ctx, u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func getSettingIntegration(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) {
|
||||
oauth, err := arcgis.GetOAuthForUser(ctx, u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get oauth", err, http.StatusInternalServerError)
|
||||
return
|
||||
return nil, newError("Failed to get oauth: %w", err)
|
||||
}
|
||||
data := contentSettingIntegration{
|
||||
ArcGISOAuth: oauth,
|
||||
URL: newContentURL(),
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/setting-integration.html", data)
|
||||
return newResponse("sync/setting-integration.html", data), nil
|
||||
}
|
||||
|
||||
type contentSettingPlaceholder struct{}
|
||||
|
||||
func getSettingPesticide(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getSettingPesticide(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return "sync/setting-pesticide.html", content, nil
|
||||
return newResponse("sync/setting-pesticide.html", content), nil
|
||||
}
|
||||
func getSettingPesticideAdd(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getSettingPesticideAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return "sync/setting-pesticide-add.html", content, nil
|
||||
return newResponse("sync/setting-pesticide-add.html", content), nil
|
||||
}
|
||||
func getSettingUserAdd(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getSettingUserAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return "sync/setting-user-add.html", content, nil
|
||||
return newResponse("sync/setting-user-add.html", content), nil
|
||||
}
|
||||
func getSettingUserList(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getSettingUserList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return "sync/setting-user-list.html", content, nil
|
||||
return newResponse("sync/setting-user-list.html", content), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ func getSignout(w http.ResponseWriter, r *http.Request, user *models.User) {
|
|||
}
|
||||
|
||||
func getSignup(w http.ResponseWriter, r *http.Request) {
|
||||
signup(w, r.URL.Path)
|
||||
data := ContentSignup{}
|
||||
html.RenderOrError(w, "sync/signup.html", data)
|
||||
}
|
||||
|
||||
func postSignin(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -105,8 +106,3 @@ func signin(w http.ResponseWriter, errorCode string, next string) {
|
|||
}
|
||||
html.RenderOrError(w, "sync/signin.html", data)
|
||||
}
|
||||
|
||||
func signup(w http.ResponseWriter, path string) {
|
||||
data := ContentSignup{}
|
||||
html.RenderOrError(w, "sync/signup.html", data)
|
||||
}
|
||||
|
|
|
|||
10
sync/sudo.go
10
sync/sudo.go
|
|
@ -19,9 +19,9 @@ type contentSudo struct {
|
|||
ForwardEmailNidusAddress string
|
||||
}
|
||||
|
||||
func getSudo(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getSudo(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSudo], *errorWithStatus) {
|
||||
if user.Role != enums.UserroleRoot {
|
||||
return "", nil, &errorWithStatus{
|
||||
return nil, &errorWithStatus{
|
||||
Message: "You have to be a root user to access this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ func getSudo(ctx context.Context, user *models.User) (string, interface{}, *erro
|
|||
ForwardEmailRMOAddress: config.ForwardEmailRMOAddress,
|
||||
ForwardEmailNidusAddress: config.ForwardEmailNidusAddress,
|
||||
}
|
||||
return "sync/sudo.html", content, nil
|
||||
return newResponse("sync/sudo.html", content), nil
|
||||
}
|
||||
|
||||
var decoder = schema.NewDecoder()
|
||||
|
|
@ -42,7 +42,7 @@ type FormEmail struct {
|
|||
To string `schema:"emailTo"`
|
||||
}
|
||||
|
||||
func postSudoEmail(ctx context.Context, u *models.User, e FormEmail) (string, *errorWithStatus) {
|
||||
func postSudoEmail(ctx context.Context, r *http.Request, u *models.User, e FormEmail) (string, *errorWithStatus) {
|
||||
if u.Role != enums.UserroleRoot {
|
||||
return "", &errorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
|
|
@ -71,7 +71,7 @@ type FormSMS struct {
|
|||
Phone string `schema:"smsPhone"`
|
||||
}
|
||||
|
||||
func postSudoSMS(ctx context.Context, u *models.User, sms FormSMS) (string, *errorWithStatus) {
|
||||
func postSudoSMS(ctx context.Context, r *http.Request, u *models.User, sms FormSMS) (string, *errorWithStatus) {
|
||||
if u.Role != enums.UserroleRoot {
|
||||
return "", &errorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
|
|
|
|||
19
sync/text.go
19
sync/text.go
|
|
@ -1,24 +1,15 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
)
|
||||
|
||||
type ContentTextMessages struct {
|
||||
User User
|
||||
}
|
||||
type contentTextMessages struct{}
|
||||
|
||||
func getTextMessages(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), u)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
content := ContentTextMessages{
|
||||
User: userContent,
|
||||
}
|
||||
html.RenderOrError(w, "sync/text-messages.html", content)
|
||||
func getTextMessages(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentTextMessages], *errorWithStatus) {
|
||||
content := contentTextMessages{}
|
||||
return newResponse("sync/text-messages.html", content), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,6 @@ type ComponentMap struct {
|
|||
Markers []MapMarker
|
||||
Zoom int
|
||||
}
|
||||
type ContentAuthenticatedPlaceholder struct {
|
||||
URL ContentURL
|
||||
User User
|
||||
}
|
||||
type ContentMockURLs struct {
|
||||
Dispatch string
|
||||
DispatchResults string
|
||||
|
|
|
|||
|
|
@ -2,13 +2,88 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type contentUploadPlaceholder struct{}
|
||||
|
||||
func getUploadList(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) {
|
||||
func getUploadList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentUploadPlaceholder], *errorWithStatus) {
|
||||
content := contentUploadPlaceholder{}
|
||||
return "sync/upload-list.html", content, nil
|
||||
return newResponse("sync/upload-list.html", content), nil
|
||||
}
|
||||
|
||||
type contentPoolDetail struct {
|
||||
CSVFileID int32
|
||||
Organization *models.Organization
|
||||
Upload platform.UploadPoolDetail
|
||||
}
|
||||
type contentUploadPoolList struct {
|
||||
Uploads []platform.PoolUpload
|
||||
}
|
||||
type contentUploadPoolCreate struct{}
|
||||
|
||||
func getUploadPoolList(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentUploadPoolList], *errorWithStatus) {
|
||||
uploads, err := platform.PoolUploadList(ctx, u.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get uploads: %w", err)
|
||||
}
|
||||
data := contentUploadPoolList{
|
||||
Uploads: uploads,
|
||||
}
|
||||
return newResponse("sync/pool-list.html", data), nil
|
||||
}
|
||||
|
||||
func getUploadPoolCreate(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentUploadPoolCreate], *errorWithStatus) {
|
||||
data := contentUploadPoolCreate{}
|
||||
return newResponse("sync/pool-csv-upload.html", data), nil
|
||||
}
|
||||
func getUploadPoolByID(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentPoolDetail], *errorWithStatus) {
|
||||
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get organization: %w", err)
|
||||
}
|
||||
file_id_str := chi.URLParam(r, "id")
|
||||
file_id, err := strconv.ParseInt(file_id_str, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to parse file_id: %w", err)
|
||||
}
|
||||
detail, err := platform.GetUploadPoolDetail(ctx, u.OrganizationID, int32(file_id))
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get pool: %w", err)
|
||||
}
|
||||
data := contentPoolDetail{
|
||||
CSVFileID: int32(file_id),
|
||||
Organization: org,
|
||||
Upload: detail,
|
||||
}
|
||||
return newResponse("sync/pool-by-id.html", data), nil
|
||||
}
|
||||
|
||||
type FormUploadPool struct{}
|
||||
|
||||
func postUploadPoolCreate(ctx context.Context, r *http.Request, u *models.User, f FormUploadPool) (string, *errorWithStatus) {
|
||||
uploads, err := userfile.SaveFileUpload(r, "csvfile", userfile.CollectionCSV)
|
||||
if err != nil {
|
||||
return "", newError("Failed to extract image uploads: %s", err)
|
||||
}
|
||||
if len(uploads) == 0 {
|
||||
return "", newErrorStatus(http.StatusBadRequest, "No upload found")
|
||||
}
|
||||
if len(uploads) != 1 {
|
||||
return "", newErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
|
||||
}
|
||||
upload := uploads[0]
|
||||
pool_upload, err := platform.NewPoolUpload(r.Context(), u, upload)
|
||||
if err != nil {
|
||||
return "", newError("Failed to create new pool: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("/pool/upload/%d", pool_upload.ID), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue