diff --git a/db/connection.go b/db/connection.go index b7344413..d0397aa3 100644 --- a/db/connection.go +++ b/db/connection.go @@ -44,14 +44,22 @@ func ExecuteNone(ctx context.Context, stmt postgres.Statement) error { func ExecuteNoneTx(ctx context.Context, txn Ex, stmt postgres.Statement) error { query, args := stmt.Sql() - _, err := txn.Query(ctx, query, args...) - return err + r, err := txn.Query(ctx, query, args...) + if err != nil { + return fmt.Errorf("query: %w", err) + } + r.Close() + return nil } func ExecuteNoneTxBob(ctx context.Context, txn bob.Tx, stmt postgres.Statement) error { query, args := stmt.Sql() - _, err := txn.QueryContext(ctx, query, args...) - return err + r, err := txn.QueryContext(ctx, query, args...) + if err != nil { + return fmt.Errorf("query: %w", err) + } + r.Close() + return nil } func ExecuteOne[T any](ctx context.Context, stmt postgres.Statement) (T, error) { query, args := stmt.Sql() diff --git a/db/query/arcgis/account.go b/db/query/arcgis/account.go index 8309ad10..d189c777 100644 --- a/db/query/arcgis/account.go +++ b/db/query/arcgis/account.go @@ -18,7 +18,7 @@ func AccountFromID(ctx context.Context, org_id string) (model.Account, error) { return db.ExecuteOne[model.Account](ctx, statement) } func AccountInsert(ctx context.Context, txn bob.Tx, m *model.Account) (model.Account, error) { - statement := table.Account.INSERT(table.Account.AllColumns). + statement := table.Account.INSERT(table.Account.MutableColumns). MODEL(m). RETURNING(table.Account.AllColumns) return db.ExecuteOneTxBob[model.Account](ctx, txn, statement) diff --git a/db/query/public/address.go b/db/query/public/address.go index 466672a3..9f359bec 100644 --- a/db/query/public/address.go +++ b/db/query/public/address.go @@ -52,6 +52,9 @@ func AddressFromID(ctx context.Context, txn db.Ex, comm_id int64) (model.Address return db.ExecuteOne[model.Address](ctx, statement) } func AddressesFromIDs(ctx context.Context, txn db.Ex, address_ids []int64) ([]model.Address, error) { + if len(address_ids) == 0 { + return []model.Address{}, nil + } sql_ids := make([]postgres.Expression, len(address_ids)) for i, address_id := range address_ids { sql_ids[i] = postgres.Int(address_id) diff --git a/db/query/publicreport/compliance.go b/db/query/publicreport/compliance.go index c5d2b555..df38265a 100644 --- a/db/query/publicreport/compliance.go +++ b/db/query/publicreport/compliance.go @@ -13,9 +13,16 @@ import ( type ComplianceUpdater = db.Updater[table.ComplianceTable, model.Compliance] +func NewComplianceUpdater() ComplianceUpdater { + return db.NewUpdater[table.ComplianceTable, model.Compliance]( + table.Compliance, + table.Compliance.ReportID, + ) +} + func NewUpdaterCompliance() db.Updater[table.ComplianceTable, model.Compliance] { return db.NewUpdater[table.ComplianceTable, model.Compliance]( - *table.Compliance, + table.Compliance, table.Compliance.ReportID, ) diff --git a/db/query/publicreport/image.go b/db/query/publicreport/image.go index dac566f1..3b0153eb 100644 --- a/db/query/publicreport/image.go +++ b/db/query/publicreport/image.go @@ -10,7 +10,7 @@ import ( ) func ImageInsert(ctx context.Context, txn db.Ex, m model.Image) (model.Image, error) { - statement := table.Image.INSERT(table.Image.AllColumns). + statement := table.Image.INSERT(table.Image.MutableColumns). MODEL(m). RETURNING(table.Image.AllColumns) return db.ExecuteOneTx[model.Image](ctx, txn, statement) diff --git a/db/query/publicreport/nuisance.go b/db/query/publicreport/nuisance.go index 34cda6cc..3f6687c6 100644 --- a/db/query/publicreport/nuisance.go +++ b/db/query/publicreport/nuisance.go @@ -11,7 +11,7 @@ import ( ) func NuisanceInsert(ctx context.Context, txn db.Ex, m model.Nuisance) (model.Nuisance, error) { - statement := table.Nuisance.INSERT(table.Nuisance.AllColumns). + statement := table.Nuisance.INSERT(table.Nuisance.MutableColumns). MODEL(m). RETURNING(table.Nuisance.AllColumns) return db.ExecuteOneTx[model.Nuisance](ctx, txn, statement) diff --git a/db/query/publicreport/report.go b/db/query/publicreport/report.go index 2a9260d9..89e9d731 100644 --- a/db/query/publicreport/report.go +++ b/db/query/publicreport/report.go @@ -15,8 +15,15 @@ import ( type ReportUpdater = db.Updater[table.ReportTable, model.Report] +func NewReportUpdater() ReportUpdater { + return db.NewUpdater[table.ReportTable, model.Report]( + table.Report, + table.Report.ID, + ) +} + func ReportInsert(ctx context.Context, txn db.Ex, m model.Report) (model.Report, error) { - statement := table.Report.INSERT(table.Report.AllColumns). + statement := table.Report.INSERT(table.Report.MutableColumns). MODEL(m). RETURNING(table.Report.AllColumns) return db.ExecuteOneTx[model.Report](ctx, txn, statement) diff --git a/db/query/publicreport/report_image.go b/db/query/publicreport/report_image.go index 523e0a03..2f067ab6 100644 --- a/db/query/publicreport/report_image.go +++ b/db/query/publicreport/report_image.go @@ -10,13 +10,13 @@ import ( ) func ReportImageInsert(ctx context.Context, txn db.Ex, m model.ReportImage) (model.ReportImage, error) { - statement := table.ReportImage.INSERT(table.ReportImage.AllColumns). + statement := table.ReportImage.INSERT(table.ReportImage.MutableColumns). MODEL(m). RETURNING(table.ReportImage.AllColumns) return db.ExecuteOneTx[model.ReportImage](ctx, txn, statement) } func ReportImagesInsert(ctx context.Context, txn db.Ex, m []model.ReportImage) ([]model.ReportImage, error) { - statement := table.ReportImage.INSERT(table.ReportImage.AllColumns). + statement := table.ReportImage.INSERT(table.ReportImage.MutableColumns). MODELS(m). RETURNING(table.ReportImage.AllColumns) return db.ExecuteManyTx[model.ReportImage](ctx, txn, statement) diff --git a/db/query/publicreport/report_log.go b/db/query/publicreport/report_log.go index c64fa3fc..bd0a2b23 100644 --- a/db/query/publicreport/report_log.go +++ b/db/query/publicreport/report_log.go @@ -10,7 +10,7 @@ import ( ) func ReportLogInsert(ctx context.Context, txn db.Ex, m model.ReportLog) (model.ReportLog, error) { - statement := table.ReportLog.INSERT(table.ReportLog.AllColumns). + statement := table.ReportLog.INSERT(table.ReportLog.MutableColumns). MODEL(m). RETURNING(table.ReportLog.AllColumns) return db.ExecuteOneTx[model.ReportLog](ctx, txn, statement) diff --git a/db/query/publicreport/water.go b/db/query/publicreport/water.go index e7b83a5d..39d497b5 100644 --- a/db/query/publicreport/water.go +++ b/db/query/publicreport/water.go @@ -11,7 +11,7 @@ import ( ) func WaterInsert(ctx context.Context, txn db.Ex, m model.Water) (model.Water, error) { - statement := table.Water.INSERT(table.Water.AllColumns). + statement := table.Water.INSERT(table.Water.MutableColumns). MODEL(m). RETURNING(table.Water.AllColumns) return db.ExecuteOneTx[model.Water](ctx, txn, statement) diff --git a/db/updater.go b/db/updater.go index 1d6fdf9f..eb36bcb0 100644 --- a/db/updater.go +++ b/db/updater.go @@ -2,6 +2,7 @@ package db import ( "context" + "fmt" //"github.com/go-jet/jet/v2" "github.com/go-jet/jet/v2/postgres" @@ -17,6 +18,13 @@ type Updater[T postgres.Table, M any] struct { } func (u Updater[T, M]) Execute(ctx context.Context, txn Ex, pk_values ...interface{}) error { + // We get syntax errors from the database if there are no updates to perform + if u.Columns == nil { + return fmt.Errorf("nil columns") + } + if len(u.Columns) == 0 { + return nil + } statement := u.Table. UPDATE(u.Columns). MODEL(u.Model). @@ -47,12 +55,12 @@ func (u *Updater[T, M]) Unset(c postgres.Column) { } } func NewUpdater[T postgres.Table, M any]( - table T, + table *T, pk_columns ...postgres.ColumnInteger, ) Updater[T, M] { return Updater[T, M]{ Columns: postgres.ColumnList{}, - Table: table, + Table: *table, buildWhere: func(pk_values ...interface{}) postgres.BoolExpression { conditions := make([]postgres.BoolExpression, len(pk_columns)) for i, col := range pk_columns { diff --git a/platform/publicreport.go b/platform/publicreport.go index 634f4616..f70e4c18 100644 --- a/platform/publicreport.go +++ b/platform/publicreport.go @@ -89,7 +89,7 @@ func PublicReportInvalid(ctx context.Context, user User, public_id string) error } now := time.Now() - report_updater := querypublicreport.ReportUpdater{} + report_updater := querypublicreport.NewReportUpdater() report_updater.Model.Reviewed = &now report_updater.Set(tablepublicreport.Report.Reviewed) reporter_id := int32(user.ID) @@ -187,6 +187,12 @@ func PublicReportUpdateCompliance(ctx context.Context, public_id string, report_ } // Avoid attempting to perform an empty update + if address != nil { + report_updates.Model.AddressGid = address.GID + report_updates.Set(tablepublicreport.Report.AddressGid) + report_updates.Model.AddressRaw = address.Raw + report_updates.Set(tablepublicreport.Report.AddressRaw) + } err = report_updates.Execute(ctx, txn, int64(report.ID)) if err != nil { return fmt.Errorf("update report: %w", err) @@ -196,7 +202,7 @@ func PublicReportUpdateCompliance(ctx context.Context, public_id string, report_ return fmt.Errorf("update compliance: %w", err) } if address != nil { - err = publicReportUpdateAddress(ctx, txn, report, *address) + err = publicReportUpdateAddressID(ctx, txn, report, *address) if err != nil { return fmt.Errorf("update address: %w", err) } @@ -224,7 +230,7 @@ func PublicReportComplianceCreate(ctx context.Context, setter_report modelpublic setter_compliance.ReportID = report_id _, err := querypublicreport.ComplianceInsert(ctx, txn, setter_compliance) if err != nil { - return fmt.Errorf("Failed to create nuisance database record: %w", err) + return fmt.Errorf("Failed to create compliance database record: %w", err) } return nil }) @@ -406,34 +412,35 @@ func publicReportCreate(ctx context.Context, setter_report modelpublicreport.Rep ) return result, nil } -func publicReportUpdateAddress(ctx context.Context, txn db.Tx, report *modelpublicreport.Report, address types.Address) error { - statement := tablepublicreport.Report.UPDATE( - tablepublicreport.Report.AddressGid, - tablepublicreport.Report.AddressRaw, - ).SET( - postgres.String(address.GID), - postgres.String(address.Raw), - ).FROM(tablepublic.Address). - WHERE( +func publicReportUpdateAddressID(ctx context.Context, txn db.Tx, report *modelpublicreport.Report, address types.Address) error { + var err error + if address.GID == "" && address.Raw != "" { + geo_res, err := geocode.GeocodeRaw(ctx, nil, address.Raw) + if err != nil { + return fmt.Errorf("Failed to geocode raw: %w", err) + } + statement := tablepublicreport.Report.UPDATE( + tablepublicreport.Report.AddressID, + ).SET( + tablepublicreport.Report.AddressID.SET(postgres.Int(int64(*geo_res.Address.ID))), + ).WHERE( tablepublicreport.Report.ID.EQ(postgres.Int(int64(report.ID))), ) - err := db.ExecuteNoneTx(ctx, txn, statement) - - if err != nil { - return fmt.Errorf("update report: %w", err) - } - statement = tablepublicreport.Report.UPDATE( - tablepublicreport.Report.AddressID, - ).SET( - tablepublic.Address.SELECT( - tablepublic.Address.ID, + err = db.ExecuteNoneTx(ctx, txn, statement) + } else { + statement := tablepublicreport.Report.UPDATE( + tablepublicreport.Report.AddressID, + ).SET( + tablepublic.Address.SELECT( + tablepublic.Address.ID, + ).WHERE( + tablepublic.Address.Gid.EQ(postgres.String(address.GID)), + ).LIMIT(1), ).WHERE( - tablepublic.Address.Gid.EQ(postgres.String(address.GID)), - ).LIMIT(1), - ).WHERE( - tablepublicreport.Report.ID.EQ(postgres.Int(int64(report.ID))), - ) - err = db.ExecuteNoneTx(ctx, txn, statement) + tablepublicreport.Report.ID.EQ(postgres.Int(int64(report.ID))), + ) + err = db.ExecuteNoneTx(ctx, txn, statement) + } if err != nil { return fmt.Errorf("update report address_id: %w", err) } diff --git a/platform/publicreport/report.go b/platform/publicreport/report.go index a2b87242..7af42b39 100644 --- a/platform/publicreport/report.go +++ b/platform/publicreport/report.go @@ -8,10 +8,10 @@ import ( "github.com/Gleipnir-Technology/bob/dialect/psql" "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/db" - querypublic "github.com/Gleipnir-Technology/nidus-sync/db/query/public" - querypublicreport "github.com/Gleipnir-Technology/nidus-sync/db/query/publicreport" 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" @@ -80,6 +80,8 @@ func reportQueryToRows(ctx context.Context, reports []modelpublicreport.Report, 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) @@ -124,30 +126,34 @@ func reportQueryToRows(ctx context.Context, reports []modelpublicreport.Report, address = &a } if address == nil { - return nil, fmt.Errorf("nil address: %w", err) + 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, + Address: *address, + Concerns: nil, + Created: row.Created, + ID: row.ID, + Images: images, + Location: location, + Log: logs, DistrictID: &row.OrganizationID, - District: nil, - PublicID: row.PublicID, + District: nil, + PublicID: row.PublicID, Reporter: types.Contact{ - CanSMS: &row.ReporterPhoneCanSms, - Email: &row.ReporterEmail, + CanSMS: &row.ReporterPhoneCanSms, + Email: &row.ReporterEmail, HasEmail: row.ReporterEmail != "", HasPhone: row.ReporterPhone != "", - Name: &row.ReporterName, - Phone: &row.ReporterPhone, + Name: &row.ReporterName, + Phone: &row.ReporterPhone, }, Status: row.Status.String(), - Type: row.ReportType.String(), - URI: "", + Type: row.ReportType.String(), + URI: "", } } return results, nil diff --git a/platform/signal.go b/platform/signal.go index 470ac770..1bff3a7d 100644 --- a/platform/signal.go +++ b/platform/signal.go @@ -152,7 +152,7 @@ func SignalCreateFromPublicreport(ctx context.Context, user User, report_id stri if err != nil { return nil, fmt.Errorf("create signal: %w", err) } - report_updater := querypublicreport.ReportUpdater{} + report_updater := querypublicreport.NewReportUpdater() now := time.Now() report_updater.Model.Reviewed = &now report_updater.Set(tablepublicreport.Report.Reviewed) diff --git a/resource/communication.go b/resource/communication.go index c50a5edb..5cc8084a 100644 --- a/resource/communication.go +++ b/resource/communication.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "strconv" + "time" "github.com/Gleipnir-Technology/nidus-sync/config" modelpublic "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/model" @@ -26,12 +27,13 @@ func Communication(r *router) *communicationR { } type communicationLog struct { - Created string `json:"string"` - ID string `json:"id"` - Type string `json:"type"` - User string `json:"user"` + Created time.Time `json:"created"` + ID string `json:"id"` + Type string `json:"type"` + User string `json:"user"` } type communication struct { + Created time.Time `json:"created"` ID string `json:"id"` Log []communicationLog `json:"log"` Response string `json:"response"` @@ -40,6 +42,14 @@ type communication struct { Type string `json:"type"` URI string `json:"uri"` } +type communicationStub struct { + Created time.Time `json:"created"` + ID string `json:"id"` + Source string `json:"source"` + Status string `json:"status"` + Type string `json:"type"` + URI string `json:"uri"` +} func toImageURLs(m map[string][]uuid.UUID, id string) []string { uuids, ok := m[id] @@ -55,7 +65,7 @@ func toImageURLs(m map[string][]uuid.UUID, id string) []string { func (res *communicationR) Get(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communication, *nhttp.ErrorWithStatus) { return nil, nil } -func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]communication, *nhttp.ErrorWithStatus) { +func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]communicationStub, *nhttp.ErrorWithStatus) { comms, err := platform.CommunicationsForOrganization(ctx, int64(user.Organization.ID)) if err != nil { return nil, nhttp.NewError("nuisance report query: %w", err) @@ -74,13 +84,13 @@ func (res *communicationR) List(ctx context.Context, r *http.Request, user platf for _, pr := range public_reports { public_report_id_to_report[pr.ID] = pr } - result := make([]communication, len(comms)) + result := make([]communicationStub, len(comms)) for i, comm := range comms { public_report, ok := public_report_id_to_report[*comm.SourceReportID] if !ok { return nil, nhttp.NewError("lookup report id %d failed", comm.SourceReportID) } - c, err := res.hydrateCommunication(comm, &public_report) + c, err := res.hydrateCommunicationStub(comm, &public_report) if err != nil { return nil, err } @@ -105,42 +115,64 @@ func (res *communicationR) MarkPossibleResolved(ctx context.Context, r *http.Req } func (res *communicationR) hydrateCommunication(comm modelpublic.Communication, public_report *modelpublicreport.Report) (communication, *nhttp.ErrorWithStatus) { var err error - source_uri := "unknown" - type_ := "unknown" - if comm.SourceReportID != nil && public_report != nil { - source_uri, err = reportURI(res.router, "", public_report.PublicID) - if err != nil { - return communication{}, nhttp.NewError("gen report URI: %w", err) - } - type_ = "publicreport." + public_report.ReportType.String() - } else if comm.SourceEmailLogID != nil { - source_uri, err = emailURI(*res.router, *comm.SourceEmailLogID) - if err != nil { - return communication{}, nhttp.NewError("gen email URI: %w", err) - } - type_ = "email" - } else if comm.SourceTextLogID != nil { - source_uri, err = textURI(*res.router, *comm.SourceTextLogID) - if err != nil { - return communication{}, nhttp.NewError("gen email URI: %w", err) - } - source_uri = "text" + stub, err := res.hydrateCommunicationStub(comm, public_report) + if err != nil { + return communication{}, nhttp.NewError("hydrate stub: %w", err) } response, err := responseURI(*res.router, comm) if err != nil { return communication{}, nhttp.NewError("gen response URI: %w", err) } + return communication{ + Created: stub.Created, + ID: stub.ID, + Response: response, + Source: stub.Source, + Status: stub.Status, + Type: stub.Type, + URI: stub.URI, + }, nil +} +func (res *communicationR) hydrateCommunicationStub(comm modelpublic.Communication, public_report *modelpublicreport.Report) (communicationStub, *nhttp.ErrorWithStatus) { + var err error + source_uri := "unknown" + type_ := "unknown" + if comm.SourceReportID != nil && public_report != nil { + source_uri, err = reportURI(res.router, "", public_report.PublicID) + if err != nil { + return communicationStub{}, nhttp.NewError("gen report URI: %w", err) + } + type_ = "publicreport." + public_report.ReportType.String() + } else if comm.SourceEmailLogID != nil { + source_uri, err = emailURI(*res.router, *comm.SourceEmailLogID) + if err != nil { + return communicationStub{}, nhttp.NewError("gen email URI: %w", err) + } + type_ = "email" + } else if comm.SourceTextLogID != nil { + source_uri, err = textURI(*res.router, *comm.SourceTextLogID) + if err != nil { + return communicationStub{}, nhttp.NewError("gen email URI: %w", err) + } + source_uri = "text" + } + /* + response, err := responseURI(*res.router, comm) + if err != nil { + return communicationStub{}, nhttp.NewError("gen response URI: %w", err) + } + */ uri, err := res.router.IDToURI("communication.ByIDGet", int(comm.ID)) if err != nil { - return communication{}, nhttp.NewError("gen comm uri: %w", err) + return communicationStub{}, nhttp.NewError("gen comm uri: %w", err) } - return communication{ - ID: strconv.Itoa(int(comm.ID)), - Response: response, - Source: source_uri, - Status: comm.Status.String(), - Type: type_, - URI: uri, + return communicationStub{ + Created: comm.Created, + ID: strconv.Itoa(int(comm.ID)), + Source: source_uri, + Status: comm.Status.String(), + Type: type_, + URI: uri, }, nil } diff --git a/resource/publicreport_compliance.go b/resource/publicreport_compliance.go index c6ad7fca..a5b01659 100644 --- a/resource/publicreport_compliance.go +++ b/resource/publicreport_compliance.go @@ -152,9 +152,9 @@ func (res *complianceR) Update(ctx context.Context, r *http.Request, prf publicR if public_id == "" { return nil, nhttp.NewBadRequest("You must provide an ID") } - report_updater := querypublicreport.ReportUpdater{} + report_updater := querypublicreport.NewReportUpdater() //report_setter := models.PublicreportReportSetter{} - compliance_updater := querypublicreport.ComplianceUpdater{} + compliance_updater := querypublicreport.NewComplianceUpdater() //compliance_setter := models.PublicreportComplianceSetter{} var location *types.Location if prf.Location.IsValue() { diff --git a/ts/AppSync.vue b/ts/AppSync.vue index f6833fa5..1e68fc5f 100644 --- a/ts/AppSync.vue +++ b/ts/AppSync.vue @@ -1,14 +1,50 @@ + + - + + + {{ error.message }} + x + + +