nidus-sync/platform/publicreport/report.go
Eli Ribble f1fe8b4d2b
Add contacts, rework comms schema
This in a pretty huge change. At a high level we're adding the concept
of a 'contact' which is a person or organization that has zero or more
contact methods (email, phone). This ended up cascading a number of
changes, including critically to the publicreprt schema. In the end it
seemed safer to get to the point where I'm confident we aren't using any
of the old fields for storing reporter information (though I haven't
deleted the columns yet) so I removed the code for defining those
columns.

At this point I think it's not possible for me to regenerate the bob
schema due to the interdependencies between my various schemas, so the
migration is well-and-truly happening.
2026-05-15 16:58:28 +00:00

198 lines
6.3 KiB
Go

package publicreport
import (
"context"
"fmt"
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
"github.com/Gleipnir-Technology/nidus-sync/db"
modelpublic "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/model"
modelpublicreport "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/model"
querypublic "github.com/Gleipnir-Technology/nidus-sync/db/query/public"
querypublicreport "github.com/Gleipnir-Technology/nidus-sync/db/query/publicreport"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
//"github.com/google/uuid"
"github.com/rs/zerolog/log"
"github.com/stephenafamo/scan"
)
func ByIDCompliance(ctx context.Context, public_id string, is_public bool) (*types.PublicReportCompliance, error) {
report, err := byID(ctx, public_id, is_public)
if err != nil {
return nil, fmt.Errorf("base report byid: %w", err)
}
if report == nil {
return nil, nil
}
return compliance(ctx, public_id, *report)
}
func ByIDNuisance(ctx context.Context, public_id string, is_public bool) (*types.PublicReportNuisance, error) {
report, err := byID(ctx, public_id, is_public)
if err != nil {
return nil, fmt.Errorf("base report byid: %w", err)
}
if report == nil {
return nil, nil
}
return nuisance(ctx, public_id, *report)
}
func ByIDWater(ctx context.Context, public_id string, is_public bool) (*types.PublicReportWater, error) {
report, err := byID(ctx, public_id, is_public)
if err != nil {
return nil, fmt.Errorf("base report byid: %w", err)
}
if report == nil {
return nil, nil
}
return water(ctx, public_id, *report)
}
func UnreviewedForOrganization(ctx context.Context, txn db.Ex, org_id int64, is_public bool) ([]types.PublicReport, error) {
reports, err := querypublicreport.ReportsUnreviewedForOrganization(ctx, txn, org_id)
if err != nil {
return nil, fmt.Errorf("reports unreviewed: %w", err)
}
return reportQueryToRows(ctx, reports, is_public)
}
func byID(ctx context.Context, public_id string, is_public bool) (*types.PublicReport, error) {
report, err := querypublicreport.ReportFromPublicID(ctx, db.PGInstance.PGXPool, public_id)
if err != nil {
return nil, fmt.Errorf("query report from public ID: %w", err)
}
if report == nil {
return nil, nil
}
reports, err := reportQueryToRows(ctx, []modelpublicreport.Report{*report}, is_public)
if err != nil {
return nil, fmt.Errorf("query to rows: %w", err)
}
log.Debug().Str("public_id", public_id).Int("len", len(reports)).Msg("querying for publicreport by ID")
if len(reports) != 1 {
return nil, nil
}
return &reports[0], nil
}
func reportQueryToRows(ctx context.Context, reports []modelpublicreport.Report, is_public bool) ([]types.PublicReport, error) {
address_ids := make([]int64, 0)
report_ids := make([]int32, len(reports))
for i, report := range reports {
report_ids[i] = report.ID
if report.AddressID != nil {
address_ids = append(address_ids, int64(*report.AddressID))
} else {
log.Debug().Int32("id", report.ID).Msg("has no address")
}
}
images_by_id, err := loadImagesForReport(ctx, report_ids)
if err != nil {
return nil, fmt.Errorf("images for report: %w", err)
}
logs_by_report_id, err := logEntriesByReportID(ctx, report_ids, is_public)
if err != nil {
return nil, fmt.Errorf("log entries for reports: %w", err)
}
addresses, err := querypublic.AddressesFromIDs(ctx, db.PGInstance.PGXPool, address_ids)
if err != nil {
return nil, fmt.Errorf("addresses for reports: %w", err)
}
addresses_by_id := make(map[int64]modelpublic.Address, 0)
for _, address := range addresses {
addresses_by_id[int64(address.ID)] = address
}
results := make([]types.PublicReport, len(reports))
for i, row := range reports {
var images []types.Image
images, ok := images_by_id[row.ID]
if !ok {
images = []types.Image{}
}
logs, ok := logs_by_report_id[row.ID]
if !ok {
return nil, fmt.Errorf("impossible, missing logs for %d", row.ID)
}
var location *types.Location
if row.Location == nil {
location = nil
}
var address *types.Address
if row.AddressID != nil {
addr, ok := addresses_by_id[int64(*row.AddressID)]
if !ok {
return nil, fmt.Errorf("impossible, missing address %d", row.AddressID)
}
a := types.AddressFromModel(addr)
address = &a
}
if address == nil {
address = &types.Address{
ID: row.AddressID,
GID: row.AddressGid,
Raw: row.AddressRaw,
}
}
results[i] = types.PublicReport{
Address: *address,
Concerns: nil,
Created: row.Created,
ID: row.ID,
Images: images,
Location: location,
Log: logs,
DistrictID: &row.OrganizationID,
District: nil,
PublicID: row.PublicID,
Reporter: types.Contact{
CanSMS: &row.ReporterPhoneCanSms,
Email: &row.ReporterEmail,
HasEmail: row.ReporterEmail != "",
HasPhone: row.ReporterPhone != "",
Name: row.ReporterName,
Phone: &row.ReporterPhone,
},
Status: row.Status.String(),
Type: row.ReportType.String(),
URI: "",
}
}
return results, nil
}
func Reports(ctx context.Context, org_id int64, ids []int64, is_public bool) ([]types.PublicReport, error) {
reports, err := querypublicreport.ReportsFromIDsForOrg(ctx, db.PGInstance.PGXPool, ids, org_id)
if err != nil {
return []types.PublicReport{}, fmt.Errorf("reports from ID for org: %w", err)
}
return reportQueryToRows(ctx, reports, is_public)
}
func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) {
type _Row struct {
Count uint `db:"count"`
}
row, err := bob.One(ctx, db.PGInstance.BobDB, psql.Select(
sm.Columns(
"COUNT(*) AS count",
),
sm.From("publicreport.report"),
sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))),
), scan.StructMapper[_Row]())
if err != nil {
return 0, fmt.Errorf("query count: %w", err)
}
return row.Count, nil
}
func copyReportContent(src types.PublicReport, dst *types.PublicReport) {
dst.Address = src.Address
dst.Created = src.Created
dst.ID = src.ID
dst.Images = src.Images
dst.Location = src.Location
dst.Log = src.Log
dst.DistrictID = src.DistrictID
dst.District = src.District
dst.PublicID = src.PublicID
dst.Reporter = src.Reporter
dst.Status = src.Status
dst.Type = src.Type
dst.URI = src.URI
}