Move all sync pages to authenticatedHandler

Still need to fix many templates
This commit is contained in:
Eli Ribble 2026-02-24 15:34:53 +00:00
parent 85d2d0b95b
commit dac52a879a
No known key found for this signature in database
23 changed files with 409 additions and 476 deletions

View file

@ -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
}

View file

@ -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>

View file

@ -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", () => {

View file

@ -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>

View file

@ -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 {

View file

@ -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
}

View file

@ -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
View 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(),
}
}

View file

@ -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) {
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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",

View file

@ -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
}

View file

@ -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

View file

@ -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
}