Separate out a public and non-public halves to publicreport APIs

This prevents us from leaking text messaging details on public
endpoints.
This commit is contained in:
Eli Ribble 2026-04-28 06:36:55 +00:00
parent 8fcd926d43
commit 8bdd18649d
No known key found for this signature in database
12 changed files with 169 additions and 136 deletions

View file

@ -26,7 +26,7 @@ type formPublicreportInvalid struct {
} }
func postPublicreportInvalid(ctx context.Context, r *http.Request, user platform.User, req formPublicreportSignal) (string, *nhttp.ErrorWithStatus) { func postPublicreportInvalid(ctx context.Context, r *http.Request, user platform.User, req formPublicreportSignal) (string, *nhttp.ErrorWithStatus) {
err := platform.PublicreportInvalid(ctx, user, req.ReportID) err := platform.PublicReportInvalid(ctx, user, req.ReportID)
if err != nil { if err != nil {
return "", nhttp.NewError("create signal: %w", err) return "", nhttp.NewError("create signal: %w", err)
} }

View file

@ -9,12 +9,59 @@ import (
func AddRoutes(r *mux.Router) { func AddRoutes(r *mux.Router) {
router := resource.NewRouter(r) router := resource.NewRouter(r)
compliance_request := resource.ComplianceRequest(router)
district := resource.District(router)
geocode := resource.Geocode(router)
lob_hook := resource.LobHook(router)
nuisance := resource.Nuisance(router)
pr_compliance := resource.PublicReportCompliance(router)
publicreport := resource.Publicreport(router)
publicreport_notification := resource.PublicreportNotification(router)
qrcode := resource.QRCode(router)
service_request := resource.ServiceRequest(router)
water := resource.Water(router)
//r.Use(render.SetContentType(render.ContentTypeJSON)) //r.Use(render.SetContentType(render.ContentTypeJSON))
// Unauthenticated endpoints // Unauthenticated endpoints
r.HandleFunc("", handlerJSON(getRoot)) r.HandleFunc("", handlerJSON(getRoot))
r.HandleFunc("/compliance-request/image/pool/{public_id}", compliance_request.ImagePoolGet).Methods("GET").Name("compliance-request.image.pool.ByIDGet")
r.Handle("/district", handlerJSONSlice(district.List)).Methods("GET")
r.Handle("/district/{id}", handlerJSON(district.GetByID)).Methods("GET").Name("district.ByIDGet")
r.HandleFunc("/district/{slug}/logo", apiGetDistrictLogo).Methods("GET").Name("district.logo.BySlug")
r.Handle("/geocode/by-gid/{id:.*}", handlerJSON(geocode.ByGID)).Methods("GET")
r.Handle("/geocode/reverse", handlerJSONPost(geocode.Reverse)).Methods("POST")
r.Handle("/geocode/reverse/closest", handlerJSONPost(geocode.ReverseClosest)).Methods("POST")
r.Handle("/geocode/suggestion", handlerJSONSlice(geocode.SuggestionList)).Methods("GET")
r.Handle("/lob/event", handlerBasic(lob_hook.Event)).Methods("POST")
r.Handle("/publicreport-notification", handlerJSONPost(publicreport_notification.Create)).Methods("POST")
r.Handle("/qr-code/mailer/{code}", handlerBasic(qrcode.Mailer)).Methods("GET")
r.Handle("/qr-code/marketing", handlerBasic(qrcode.Marketing)).Methods("GET")
r.Handle("/qr-code/report/{code}", handlerBasic(qrcode.Report)).Methods("GET")
r.HandleFunc("/rmo/compliance", handlerJSONPost(pr_compliance.Create)).Methods("POST")
r.HandleFunc("/rmo/nuisance", handlerFormPost(nuisance.Create)).Methods("POST")
r.Handle("/rmo/publicreport/{id}", handlerBasic(publicreport.ByIDPublic)).Methods("GET").Name("publicreport.ByIDGetPublic")
r.Handle("/rmo/publicreport/compliance/{id}/image", handlerFormPost(publicreport.ImageCreate)).Methods("POST")
r.Handle("/rmo/publicreport/compliance/{id}", handlerJSON(pr_compliance.ByIDPublic)).Methods("GET").Name("publicreport.compliance.ByIDGetPublic")
r.Handle("/rmo/publicreport/compliance/{id}", handlerJSONPut(pr_compliance.Update)).Methods("PUT")
r.Handle("/rmo/publicreport/nuisance/{id}", handlerJSON(nuisance.ByIDPublic)).Methods("GET").Name("publicreport.nuisance.ByIDGetPublic")
r.Handle("/rmo/publicreport/water/{id}", handlerJSON(water.ByIDPublic)).Methods("GET").Name("publicreport.water.ByIDGet")
r.Handle("/rmo/publicreport/{id}", handlerBasic(publicreport.ByIDPublic)).Methods("GET").Name("publicreport.ByIDGetPublicPublic")
r.HandleFunc("/rmo/water", handlerFormPost(water.Create)).Methods("POST")
r.HandleFunc("/signin", handlerJSONPost(postSignin)) r.HandleFunc("/signin", handlerJSONPost(postSignin))
r.Handle("/signout", authenticatedHandlerBasic(postSignout)) r.Handle("/signout", authenticatedHandlerBasic(postSignout))
r.HandleFunc("/signup", handlerJSONPost(postSignup)) r.HandleFunc("/signup", handlerJSONPost(postSignup))
r.HandleFunc("/twilio/call", twilioCallPost).Methods("POST")
r.HandleFunc("/twilio/call/status", twilioCallStatusPost).Methods("POST")
r.HandleFunc("/twilio/message", twilioMessagePost).Methods("POST")
r.HandleFunc("/twilio/text", twilioTextPost).Methods("POST")
r.HandleFunc("/twilio/text/status", twilioTextStatusPost).Methods("POST")
r.HandleFunc("/voipms/text", voipmsTextGet).Methods("GET")
r.HandleFunc("/voipms/text", voipmsTextPost).Methods("POST")
r.HandleFunc("/webhook/fieldseeker", webhookFieldseeker).Methods("GET")
r.HandleFunc("/webhook/fieldseeker", webhookFieldseeker).Methods("POST")
// Authenticated endpoints // Authenticated endpoints
r.Handle("/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost)).Methods("POST") r.Handle("/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost)).Methods("POST")
r.Handle("/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost)).Methods("POST") r.Handle("/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost)).Methods("POST")
@ -24,9 +71,7 @@ func AddRoutes(r *mux.Router) {
r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET") r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET")
communication := resource.Communication(router) communication := resource.Communication(router)
r.Handle("/communication", authenticatedHandlerJSON(communication.List)).Methods("GET") r.Handle("/communication", authenticatedHandlerJSON(communication.List)).Methods("GET")
compliance_request := resource.ComplianceRequest(router)
r.Handle("/compliance-request/mailer", authenticatedHandlerJSONPost(compliance_request.CreateMailer)).Methods("POST") r.Handle("/compliance-request/mailer", authenticatedHandlerJSONPost(compliance_request.CreateMailer)).Methods("POST")
r.HandleFunc("/compliance-request/image/pool/{public_id}", compliance_request.ImagePoolGet).Methods("GET").Name("compliance-request.image.pool.ByIDGet")
//r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET") //r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET")
r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST") r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST")
r.Handle("/events", auth.NewEnsureAuth(streamEvents)).Methods("GET") r.Handle("/events", auth.NewEnsureAuth(streamEvents)).Methods("GET")
@ -39,32 +84,24 @@ func AddRoutes(r *mux.Router) {
lead := resource.Lead(r) lead := resource.Lead(r)
r.Handle("/leads", authenticatedHandlerJSON(lead.List)).Methods("GET") r.Handle("/leads", authenticatedHandlerJSON(lead.List)).Methods("GET")
r.Handle("/leads", authenticatedHandlerJSONPost(lead.Create)).Methods("POST") r.Handle("/leads", authenticatedHandlerJSONPost(lead.Create)).Methods("POST")
lob_hook := resource.LobHook(router)
r.Handle("/lob/event", handlerBasic(lob_hook.Event)).Methods("POST")
mailer := resource.Mailer(router) mailer := resource.Mailer(router)
r.Handle("/mailer", authenticatedHandlerJSONSlice(mailer.List)).Methods("GET") r.Handle("/mailer", authenticatedHandlerJSONSlice(mailer.List)).Methods("GET")
r.Handle("/mailer/{id}", authenticatedHandlerJSONPost(mailer.ByIDGet)).Methods("GET").Name("mailer.ByIDGet") r.Handle("/mailer/{id}", authenticatedHandlerJSONPost(mailer.ByIDGet)).Methods("GET").Name("mailer.ByIDGet")
r.Handle("/mosquito-source", auth.NewEnsureAuth(apiMosquitoSource)).Methods("GET") r.Handle("/mosquito-source", auth.NewEnsureAuth(apiMosquitoSource)).Methods("GET")
qrcode := resource.QRCode(router)
r.Handle("/qr-code/mailer/{code}", handlerBasic(qrcode.Mailer)).Methods("GET")
r.Handle("/qr-code/marketing", handlerBasic(qrcode.Marketing)).Methods("GET")
r.Handle("/qr-code/report/{code}", handlerBasic(qrcode.Report)).Methods("GET")
r.Handle("/publicreport/invalid", authenticatedHandlerJSONPost(postPublicreportInvalid)).Methods("POST") r.Handle("/publicreport/invalid", authenticatedHandlerJSONPost(postPublicreportInvalid)).Methods("POST")
r.Handle("/publicreport/signal", authenticatedHandlerJSONPost(postPublicreportSignal)).Methods("POST") r.Handle("/publicreport/signal", authenticatedHandlerJSONPost(postPublicreportSignal)).Methods("POST")
r.Handle("/publicreport/message", authenticatedHandlerJSONPost(postPublicreportMessage)).Methods("POST") r.Handle("/publicreport/message", authenticatedHandlerJSONPost(postPublicreportMessage)).Methods("POST")
r.Handle("/publicreport/{id}", authenticatedHandlerBasic(publicreport.ByID)).Methods("GET").Name("publicreport.ByIDGetPublic")
r.Handle("/publicreport/compliance/{id}", authenticatedHandlerJSON(pr_compliance.ByID)).Methods("GET").Name("publicreport.compliance.ByIDGet")
r.Handle("/publicreport/nuisance/{id}", authenticatedHandlerJSON(nuisance.ByID)).Methods("GET").Name("publicreport.nuisance.ByIDGet")
r.Handle("/publicreport/water/{id}", authenticatedHandlerJSON(water.ByID)).Methods("GET").Name("publicreport.water.ByIDGet")
r.Handle("/publicreport-notification", handlerJSONPost(publicreport_notification.Create)).Methods("POST")
r.Handle("/review/pool", authenticatedHandlerJSONPost(postReviewPool)).Methods("POST") r.Handle("/review/pool", authenticatedHandlerJSONPost(postReviewPool)).Methods("POST")
review_task := resource.ReviewTask(r) review_task := resource.ReviewTask(r)
r.Handle("/review-task", authenticatedHandlerJSON(review_task.List)).Methods("GET") r.Handle("/review-task", authenticatedHandlerJSON(review_task.List)).Methods("GET")
pr_compliance := resource.PublicReportCompliance(router)
r.HandleFunc("/rmo/compliance", handlerJSONPost(pr_compliance.Create)).Methods("POST")
nuisance := resource.Nuisance(router)
r.HandleFunc("/rmo/nuisance", handlerFormPost(nuisance.Create)).Methods("POST")
water := resource.Water(router)
r.HandleFunc("/rmo/water", handlerFormPost(water.Create)).Methods("POST")
service_request := resource.ServiceRequest(router)
r.Handle("/service-request", authenticatedHandlerJSONSlice(service_request.List)).Methods("GET") r.Handle("/service-request", authenticatedHandlerJSONSlice(service_request.List)).Methods("GET")
session := resource.Session(router) session := resource.Session(router)
r.Handle("/session", authenticatedHandlerJSON(session.Get)).Methods("GET").Name("session.get") r.Handle("/session", authenticatedHandlerJSON(session.Get)).Methods("GET").Name("session.get")
@ -96,34 +133,4 @@ func AddRoutes(r *mux.Router) {
r.Handle("/user/{id}", authenticatedHandlerJSONPut(user.ByIDPut)).Methods("PUT") r.Handle("/user/{id}", authenticatedHandlerJSONPut(user.ByIDPut)).Methods("PUT")
// Unauthenticated endpoints // Unauthenticated endpoints
district := resource.District(router)
r.Handle("/district", handlerJSONSlice(district.List)).Methods("GET")
r.Handle("/district/{id}", handlerJSON(district.GetByID)).Methods("GET").Name("district.ByIDGet")
geocode := resource.Geocode(router)
r.Handle("/geocode/by-gid/{id:.*}", handlerJSON(geocode.ByGID)).Methods("GET")
r.Handle("/geocode/reverse", handlerJSONPost(geocode.Reverse)).Methods("POST")
r.Handle("/geocode/reverse/closest", handlerJSONPost(geocode.ReverseClosest)).Methods("POST")
r.Handle("/geocode/suggestion", handlerJSONSlice(geocode.SuggestionList)).Methods("GET")
publicreport := resource.Publicreport(router)
r.Handle("/publicreport/{id}", handlerBasic(publicreport.ByID)).Methods("GET").Name("publicreport.ByIDGet")
r.Handle("/publicreport/compliance/{id}/image", handlerFormPost(publicreport.ImageCreate)).Methods("POST")
r.Handle("/publicreport/compliance/{id}", handlerJSON(pr_compliance.ByID)).Methods("GET").Name("publicreport.compliance.ByIDGet")
r.Handle("/publicreport/compliance/{id}", handlerJSONPut(pr_compliance.Update)).Methods("PUT")
r.Handle("/publicreport/nuisance/{id}", handlerJSON(nuisance.ByID)).Methods("GET").Name("publicreport.nuisance.ByIDGet")
r.Handle("/publicreport/water/{id}", handlerJSON(water.ByID)).Methods("GET").Name("publicreport.water.ByIDGet")
publicreport_notification := resource.PublicreportNotification(router)
r.Handle("/publicreport-notification", handlerJSONPost(publicreport_notification.Create)).Methods("POST")
//r.HandleFunc("/district", apiGetDistrict).Methods("GET")
r.HandleFunc("/district/{slug}/logo", apiGetDistrictLogo).Methods("GET").Name("district.logo.BySlug")
r.HandleFunc("/twilio/call", twilioCallPost).Methods("POST")
r.HandleFunc("/twilio/call/status", twilioCallStatusPost).Methods("POST")
r.HandleFunc("/twilio/message", twilioMessagePost).Methods("POST")
r.HandleFunc("/twilio/text", twilioTextPost).Methods("POST")
r.HandleFunc("/twilio/text/status", twilioTextStatusPost).Methods("POST")
r.HandleFunc("/voipms/text", voipmsTextGet).Methods("GET")
r.HandleFunc("/voipms/text", voipmsTextPost).Methods("POST")
r.HandleFunc("/webhook/fieldseeker", webhookFieldseeker).Methods("GET")
r.HandleFunc("/webhook/fieldseeker", webhookFieldseeker).Methods("POST")
} }

View file

@ -53,11 +53,11 @@ func GenerateReportID() (string, error) {
return builder.String(), nil return builder.String(), nil
} }
func PublicreportByID(ctx context.Context, report_id string) (*types.PublicReport, error) { func PublicReportByID(ctx context.Context, report_id string, is_public bool) (*types.PublicReport, error) {
return publicreport.ByID(ctx, report_id) return publicreport.ByID(ctx, report_id, is_public)
} }
func PublicreportByIDCompliance(ctx context.Context, report_id string) (*types.PublicReportCompliance, error) { func PublicReportByIDCompliance(ctx context.Context, report_id string, is_public bool) (*types.PublicReportCompliance, error) {
result, err := publicreport.ByIDCompliance(ctx, report_id) result, err := publicreport.ByIDCompliance(ctx, report_id, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("byidcompliance: %w", err) return nil, fmt.Errorf("byidcompliance: %w", err)
} }
@ -75,14 +75,14 @@ func PublicreportByIDCompliance(ctx context.Context, report_id string) (*types.P
} }
return result, nil return result, nil
} }
func PublicreportByIDNuisance(ctx context.Context, report_id string) (*types.PublicReportNuisance, error) { func PublicReportByIDNuisance(ctx context.Context, report_id string, is_public bool) (*types.PublicReportNuisance, error) {
return publicreport.ByIDNuisance(ctx, report_id) return publicreport.ByIDNuisance(ctx, report_id, is_public)
} }
func PublicreportByIDWater(ctx context.Context, report_id string) (*types.PublicReportWater, error) { func PublicReportByIDWater(ctx context.Context, report_id string, is_public bool) (*types.PublicReportWater, error) {
return publicreport.ByIDWater(ctx, report_id) return publicreport.ByIDWater(ctx, report_id, is_public)
} }
func PublicreportComplianceSubmit(ctx context.Context, report_id string) (*types.PublicReportCompliance, error) { func PublicReportComplianceSubmit(ctx context.Context, report_id string, is_public bool) (*types.PublicReportCompliance, error) {
report, err := publicreport.ByIDCompliance(ctx, report_id) report, err := publicreport.ByIDCompliance(ctx, report_id, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("byidcompliance: %w", err) return nil, fmt.Errorf("byidcompliance: %w", err)
} }
@ -94,9 +94,9 @@ func PublicreportComplianceSubmit(ctx context.Context, report_id string) (*types
if err != nil { if err != nil {
return nil, fmt.Errorf("update report submitted: %w", err) return nil, fmt.Errorf("update report submitted: %w", err)
} }
return publicreport.ByIDCompliance(ctx, report_id) return publicreport.ByIDCompliance(ctx, report_id, is_public)
} }
func PublicreportInvalid(ctx context.Context, user User, public_id string) error { func PublicReportInvalid(ctx context.Context, user User, public_id string) error {
report, err := publicReportFromID(ctx, public_id) report, err := publicReportFromID(ctx, public_id)
if err != nil { if err != nil {
return fmt.Errorf("query report existence: %w", err) return fmt.Errorf("query report existence: %w", err)
@ -206,13 +206,13 @@ func PublicReportUpdateCompliance(ctx context.Context, public_id string, report_
} }
} }
txn.Commit(ctx) txn.Commit(ctx)
return publicreport.ByIDCompliance(ctx, public_id) return publicreport.ByIDCompliance(ctx, public_id, false)
} }
func PublicReportReporterUpdated(ctx context.Context, org_id int32, report_id string) { func PublicReportReporterUpdated(ctx context.Context, org_id int32, report_id string) {
event.Updated(event.TypeRMOPublicReport, org_id, report_id) event.Updated(event.TypeRMOPublicReport, org_id, report_id)
} }
func PublicReportsForOrganization(ctx context.Context, org_id int32) ([]*types.PublicReport, error) { func PublicReportsForOrganization(ctx context.Context, org_id int32, is_public bool) ([]*types.PublicReport, error) {
return publicreport.ReportsForOrganization(ctx, org_id) return publicreport.ReportsForOrganization(ctx, org_id, is_public)
} }
func PublicReportComplianceCreate(ctx context.Context, setter_report models.PublicreportReportSetter, setter_compliance models.PublicreportComplianceSetter, org_id int32) (*models.PublicreportReport, error) { func PublicReportComplianceCreate(ctx context.Context, setter_report models.PublicreportReportSetter, setter_compliance models.PublicreportComplianceSetter, org_id int32) (*models.PublicreportReport, error) {
return publicReportCreate(ctx, setter_report, nil, nil, nil, org_id, func(ctx context.Context, txn bob.Executor, report_id int32) error { return publicReportCreate(ctx, setter_report, nil, nil, nil, org_id, func(ctx context.Context, txn bob.Executor, report_id int32) error {

View file

@ -15,7 +15,7 @@ import (
"github.com/stephenafamo/scan" "github.com/stephenafamo/scan"
) )
func logEntriesByReportID(ctx context.Context, report_ids []int32) (map[int32][]*types.LogEntry, error) { func logEntriesByReportID(ctx context.Context, report_ids []int32, is_public bool) (map[int32][]*types.LogEntry, error) {
results := make(map[int32][]*types.LogEntry, len(report_ids)) results := make(map[int32][]*types.LogEntry, len(report_ids))
for _, report_id := range report_ids { for _, report_id := range report_ids {
results[report_id] = make([]*types.LogEntry, 0) results[report_id] = make([]*types.LogEntry, 0)
@ -49,6 +49,7 @@ func logEntriesByReportID(ctx context.Context, report_ids []int32) (map[int32][]
for _, row := range rows { for _, row := range rows {
results[row.ReportID] = append(results[row.ReportID], &row) results[row.ReportID] = append(results[row.ReportID], &row)
} }
if !is_public {
logs_from_texts, err := logEntriesFromTexts(ctx, report_ids) logs_from_texts, err := logEntriesFromTexts(ctx, report_ids)
if err != nil { if err != nil {
return results, fmt.Errorf("log from texts: %w", err) return results, fmt.Errorf("log from texts: %w", err)
@ -63,6 +64,7 @@ func logEntriesByReportID(ctx context.Context, report_ids []int32) (map[int32][]
} }
results[report_id] = cur_logs results[report_id] = cur_logs
} }
}
return results, nil return results, nil
} }

View file

@ -15,12 +15,12 @@ import (
"github.com/stephenafamo/scan" "github.com/stephenafamo/scan"
) )
func ByID(ctx context.Context, public_id string) (*types.PublicReport, error) { func ByID(ctx context.Context, public_id string, is_public bool) (*types.PublicReport, error) {
query := reportQuery() query := reportQuery()
query.Apply( query.Apply(
sm.Where(psql.Quote("r", "public_id").EQ(psql.Arg(public_id))), sm.Where(psql.Quote("r", "public_id").EQ(psql.Arg(public_id))),
) )
reports, err := reportQueryToRows(ctx, query) reports, err := reportQueryToRows(ctx, query, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("query to rows: %w", err) return nil, fmt.Errorf("query to rows: %w", err)
} }
@ -30,36 +30,36 @@ func ByID(ctx context.Context, public_id string) (*types.PublicReport, error) {
} }
return reports[0], nil return reports[0], nil
} }
func ByIDCompliance(ctx context.Context, public_id string) (*types.PublicReportCompliance, error) { func ByIDCompliance(ctx context.Context, public_id string, is_public bool) (*types.PublicReportCompliance, error) {
report, err := ByID(ctx, public_id) report, err := ByID(ctx, public_id, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("base report byid: %w", err) return nil, fmt.Errorf("base report byid: %w", err)
} }
return compliance(ctx, public_id, report) return compliance(ctx, public_id, report)
} }
func ByIDNuisance(ctx context.Context, public_id string) (*types.PublicReportNuisance, error) { func ByIDNuisance(ctx context.Context, public_id string, is_public bool) (*types.PublicReportNuisance, error) {
report, err := ByID(ctx, public_id) report, err := ByID(ctx, public_id, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("base report byid: %w", err) return nil, fmt.Errorf("base report byid: %w", err)
} }
return nuisance(ctx, public_id, report) return nuisance(ctx, public_id, report)
} }
func ByIDWater(ctx context.Context, public_id string) (*types.PublicReportWater, error) { func ByIDWater(ctx context.Context, public_id string, is_public bool) (*types.PublicReportWater, error) {
report, err := ByID(ctx, public_id) report, err := ByID(ctx, public_id, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("base report byid: %w", err) return nil, fmt.Errorf("base report byid: %w", err)
} }
return water(ctx, public_id, report) return water(ctx, public_id, report)
} }
func ReportsForOrganization(ctx context.Context, org_id int32) ([]*types.PublicReport, error) { func ReportsForOrganization(ctx context.Context, org_id int32, is_public bool) ([]*types.PublicReport, error) {
query := reportQuery() query := reportQuery()
query.Apply( query.Apply(
sm.Where(psql.Quote("r", "organization_id").EQ(psql.Arg(org_id))), sm.Where(psql.Quote("r", "organization_id").EQ(psql.Arg(org_id))),
sm.Where(psql.Quote("r", "reviewed").IsNull()), sm.Where(psql.Quote("r", "reviewed").IsNull()),
) )
return reportQueryToRows(ctx, query) return reportQueryToRows(ctx, query, is_public)
} }
func reportQueryToRows(ctx context.Context, query bob.BaseQuery[*dialect.SelectQuery]) ([]*types.PublicReport, error) { func reportQueryToRows(ctx context.Context, query bob.BaseQuery[*dialect.SelectQuery], is_public bool) ([]*types.PublicReport, error) {
rows, err := bob.All(ctx, db.PGInstance.BobDB, query, scan.StructMapper[types.PublicReport]()) rows, err := bob.All(ctx, db.PGInstance.BobDB, query, scan.StructMapper[types.PublicReport]())
if err != nil { if err != nil {
@ -73,7 +73,7 @@ func reportQueryToRows(ctx context.Context, query bob.BaseQuery[*dialect.SelectQ
if err != nil { if err != nil {
return nil, fmt.Errorf("images for report: %w", err) return nil, fmt.Errorf("images for report: %w", err)
} }
logs_by_report_id, err := logEntriesByReportID(ctx, report_ids) logs_by_report_id, err := logEntriesByReportID(ctx, report_ids, is_public)
if err != nil { if err != nil {
return nil, fmt.Errorf("log entries for reports: %w", err) return nil, fmt.Errorf("log entries for reports: %w", err)
} }
@ -94,13 +94,13 @@ func reportQueryToRows(ctx context.Context, query bob.BaseQuery[*dialect.SelectQ
} }
return results, nil return results, nil
} }
func Reports(ctx context.Context, org_id int32, ids []int32) ([]*types.PublicReport, error) { func Reports(ctx context.Context, org_id int32, ids []int32, is_public bool) ([]*types.PublicReport, error) {
query := reportQuery() query := reportQuery()
query.Apply( query.Apply(
sm.Where(psql.Quote("r", "organization_id").EQ(psql.Arg(org_id))), sm.Where(psql.Quote("r", "organization_id").EQ(psql.Arg(org_id))),
sm.Where(psql.Quote("r", "id").EQ(psql.Any(ids))), sm.Where(psql.Quote("r", "id").EQ(psql.Any(ids))),
) )
return reportQueryToRows(ctx, query) return reportQueryToRows(ctx, query, is_public)
} }
func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) { func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) {
type _Row struct { type _Row struct {

View file

@ -275,7 +275,7 @@ func SignalList(ctx context.Context, user User, limit int) ([]*Signal, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("getting pools by ID: %w", err) return nil, fmt.Errorf("getting pools by ID: %w", err)
} }
reports, err := publicreport.Reports(ctx, org_id, report_ids) reports, err := publicreport.Reports(ctx, org_id, report_ids, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("getting reports by ID: %w", err) return nil, fmt.Errorf("getting reports by ID: %w", err)
} }

View file

@ -47,7 +47,7 @@ func toImageURLs(m map[string][]uuid.UUID, id string) []string {
return urls return urls
} }
func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communicationList, *nhttp.ErrorWithStatus) { func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communicationList, *nhttp.ErrorWithStatus) {
reports, err := platform.PublicReportsForOrganization(ctx, user.Organization.ID) reports, err := platform.PublicReportsForOrganization(ctx, user.Organization.ID, false)
if err != nil { if err != nil {
return nil, nhttp.NewError("nuisance report query: %w", err) return nil, nhttp.NewError("nuisance report query: %w", err)
} }

View file

@ -23,7 +23,7 @@ func Publicreport(r *router) *publicreportR {
} }
} }
func (res *publicreportR) ByID(ctx context.Context, w http.ResponseWriter, r *http.Request) *nhttp.ErrorWithStatus { func (res *publicreportR) ByID(ctx context.Context, w http.ResponseWriter, r *http.Request, u platform.User) *nhttp.ErrorWithStatus {
vars := mux.Vars(r) vars := mux.Vars(r)
public_id := vars["id"] public_id := vars["id"]
if public_id == "" { if public_id == "" {
@ -40,6 +40,23 @@ func (res *publicreportR) ByID(ctx context.Context, w http.ResponseWriter, r *ht
http.Redirect(w, r, path, http.StatusFound) http.Redirect(w, r, path, http.StatusFound)
return nil return nil
} }
func (res *publicreportR) ByIDPublic(ctx context.Context, w http.ResponseWriter, r *http.Request) *nhttp.ErrorWithStatus {
vars := mux.Vars(r)
public_id := vars["id"]
if public_id == "" {
return nhttp.NewBadRequest("You must provide an ID")
}
report_type, err := platform.PublicReportTypeByID(ctx, public_id)
if err != nil {
return nhttp.NewError("get report '%s': %w", public_id, err)
}
path, err := reportURIPublic(res.router, report_type, public_id)
if err != nil {
return nhttp.NewError("get uri '%s': %w", public_id, err)
}
http.Redirect(w, r, path, http.StatusFound)
return nil
}
type image struct { type image struct {
Status string `json:"status"` Status string `json:"status"`
@ -100,3 +117,21 @@ func reportURI(r *router, report_type string, public_id string) (string, error)
} }
return uri, nil return uri, nil
} }
func reportURIPublic(r *router, report_type string, public_id string) (string, error) {
var route_name string
switch report_type {
case "compliance":
route_name = "publicreport.compliance.ByIDGetPublic"
case "nuisance":
route_name = "publicreport.nuisance.ByIDGetPublic"
case "water":
route_name = "publicreport.water.ByIDGetPublic"
default:
return "", fmt.Errorf("Unrecognized report type '%s'", report_type)
}
uri, err := r.IDStrToURI(route_name, public_id)
if err != nil {
return "", fmt.Errorf("id str to uri '%s' '%s': %w", route_name, public_id, err)
}
return uri, nil
}

View file

@ -45,13 +45,16 @@ type publicreportComplianceForm struct {
WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"` WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"`
} }
func (res *complianceR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) { func (res *complianceR) ByID(ctx context.Context, r *http.Request, u platform.User, query QueryParams) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) {
return res.ByIDPublic(ctx, r, query)
}
func (res *complianceR) ByIDPublic(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r) vars := mux.Vars(r)
public_id := vars["id"] public_id := vars["id"]
if public_id == "" { if public_id == "" {
return nil, nhttp.NewBadRequest("You must provid an ID") return nil, nhttp.NewBadRequest("You must provid an ID")
} }
report, err := platform.PublicreportByIDCompliance(ctx, public_id) report, err := platform.PublicReportByIDCompliance(ctx, public_id, true)
if err != nil { if err != nil {
return nil, nhttp.NewError("get report: %w", err) return nil, nhttp.NewError("get report: %w", err)
} }
@ -132,7 +135,7 @@ func (res *complianceR) Create(ctx context.Context, r *http.Request, n publicrep
return nil, nhttp.NewError("create compliance report: %w", err) return nil, nhttp.NewError("create compliance report: %w", err)
} }
// Return a fully-fleshed-out report object, even though it's a bit more expensive // Return a fully-fleshed-out report object, even though it's a bit more expensive
result, err := platform.PublicreportByIDCompliance(ctx, report.PublicID) result, err := platform.PublicReportByIDCompliance(ctx, report.PublicID, true)
if err != nil { if err != nil {
return nil, nhttp.NewError("get report after creation: %w", err) return nil, nhttp.NewError("get report after creation: %w", err)
} }
@ -206,7 +209,7 @@ func (res *complianceR) Update(ctx context.Context, r *http.Request, prf publicr
return nil, nhttp.NewError("platform update report compliance: %w", err) return nil, nhttp.NewError("platform update report compliance: %w", err)
} }
// Return a fully-fleshed-out report object, even though it's a bit more expensive // Return a fully-fleshed-out report object, even though it's a bit more expensive
report, err = platform.PublicreportByIDCompliance(ctx, public_id) report, err = platform.PublicReportByIDCompliance(ctx, public_id, true)
if err != nil { if err != nil {
return nil, nhttp.NewError("get report after update: %w", err) return nil, nhttp.NewError("get report after update: %w", err)
} }
@ -223,7 +226,7 @@ func (res *complianceR) Submit(ctx context.Context, r *http.Request, prf publicr
if public_id == "" { if public_id == "" {
return nil, nhttp.NewBadRequest("You must provide an ID") return nil, nhttp.NewBadRequest("You must provide an ID")
} }
report, err := platform.PublicreportComplianceSubmit(ctx, public_id) report, err := platform.PublicReportComplianceSubmit(ctx, public_id, true)
if err != nil { if err != nil {
return nil, nhttp.NewError("submit report: %w", err) return nil, nhttp.NewError("submit report: %w", err)
} }

View file

@ -51,13 +51,16 @@ type nuisanceForm struct {
TODNight bool `schema:"tod-night"` TODNight bool `schema:"tod-night"`
} }
func (res *nuisanceR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportNuisance, *nhttp.ErrorWithStatus) { func (res *nuisanceR) ByID(ctx context.Context, r *http.Request, u platform.User, query QueryParams) (*types.PublicReportNuisance, *nhttp.ErrorWithStatus) {
return res.ByIDPublic(ctx, r, query)
}
func (res *nuisanceR) ByIDPublic(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportNuisance, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r) vars := mux.Vars(r)
public_id := vars["id"] public_id := vars["id"]
if public_id == "" { if public_id == "" {
return nil, nhttp.NewBadRequest("You must provid an ID") return nil, nhttp.NewBadRequest("You must provid an ID")
} }
report, err := platform.PublicreportByIDNuisance(ctx, public_id) report, err := platform.PublicReportByIDNuisance(ctx, public_id, true)
if err != nil { if err != nil {
return nil, nhttp.NewError("get report: %w", err) return nil, nhttp.NewError("get report: %w", err)
} }

View file

@ -56,19 +56,11 @@ type waterForm struct {
OwnerPhone string `schema:"owner-phone"` OwnerPhone string `schema:"owner-phone"`
} }
func (res *waterR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportWater, *nhttp.ErrorWithStatus) { func (res *waterR) ByID(ctx context.Context, r *http.Request, u platform.User, query QueryParams) (*types.PublicReportWater, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r) return res.byID(ctx, r, false)
public_id := vars["id"] }
if public_id == "" { func (res *waterR) ByIDPublic(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportWater, *nhttp.ErrorWithStatus) {
return nil, nhttp.NewBadRequest("You must provid an ID") return res.byID(ctx, r, true)
}
report, err := platform.PublicreportByIDWater(ctx, public_id)
if err != nil {
return nil, nhttp.NewError("get report: %w", err)
}
populateDistrictURI(&report.PublicReport, res.router)
populateReportURI(&report.PublicReport, res.router)
return report, nil
} }
func (res *waterR) Create(ctx context.Context, r *http.Request, w waterForm) (*water, *nhttp.ErrorWithStatus) { func (res *waterR) Create(ctx context.Context, r *http.Request, w waterForm) (*water, *nhttp.ErrorWithStatus) {
@ -146,3 +138,17 @@ func (res *waterR) Create(ctx context.Context, r *http.Request, w waterForm) (*w
URI: uri, URI: uri,
}, nil }, nil
} }
func (res *waterR) byID(ctx context.Context, r *http.Request, is_public bool) (*types.PublicReportWater, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r)
public_id := vars["id"]
if public_id == "" {
return nil, nhttp.NewBadRequest("You must provid an ID")
}
report, err := platform.PublicReportByIDWater(ctx, public_id, is_public)
if err != nil {
return nil, nhttp.NewError("get report: %w", err)
}
populateDistrictURI(&report.PublicReport, res.router)
populateReportURI(&report.PublicReport, res.router)
return report, nil
}

View file

@ -146,7 +146,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from "vue"; import { ref, computed } from "vue";
import { computedAsync } from "@vueuse/core"; import { computedAsync } from "@vueuse/core";
import Header from "@/rmo/components/Header.vue"; import Header from "@/rmo/components/Header.vue";
import HeaderDistrict from "@/components/HeaderDistrict.vue"; import HeaderDistrict from "@/components/HeaderDistrict.vue";
@ -154,7 +154,7 @@ import MapLocatorDisplay from "@/components/MapLocatorDisplay.vue";
import ReportDetailNuisance from "@/rmo/components/ReportDetailNuisance.vue"; import ReportDetailNuisance from "@/rmo/components/ReportDetailNuisance.vue";
import ReportDetailWater from "@/rmo/components/ReportDetailWater.vue"; import ReportDetailWater from "@/rmo/components/ReportDetailWater.vue";
import { useStoreDistrict } from "@/rmo/store/district"; import { useStoreDistrict } from "@/rmo/store/district";
import { useStorePublicReport } from "@/store/publicreport"; import { useStorePublicReport } from "@/rmo/store/publicreport";
import type { Marker } from "@/types"; import type { Marker } from "@/types";
import { import {
type District, type District,
@ -200,27 +200,4 @@ const markers = computed((): Marker[] => {
}, },
]; ];
}); });
// Lifecycle
onMounted(() => {
// Load map scripts if needed
loadMapScripts();
});
const loadMapScripts = () => {
// Load MapLibre GL if not already loaded
if (!document.querySelector('script[src*="maplibre-gl"]')) {
const script1 = document.createElement("script");
script1.src = "//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.js";
document.head.appendChild(script1);
}
// Load Mapbox GL if not already loaded
if (!document.querySelector('script[src*="mapbox-gl"]')) {
const script2 = document.createElement("script");
script2.src =
"https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js";
document.head.appendChild(script2);
}
};
</script> </script>