diff --git a/api/signal.go b/api/signal.go index 266bc5e1..8cdc31e1 100644 --- a/api/signal.go +++ b/api/signal.go @@ -10,7 +10,7 @@ import ( ) type contentListSignal struct { - Signals []platform.Signal `json:"signals"` + Signals []*platform.Signal `json:"signals"` } func listSignal(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListSignal, *nhttp.ErrorWithStatus) { diff --git a/db/dbinfo/signal.bob.go b/db/dbinfo/signal.bob.go index 92d94e18..f6786ae0 100644 --- a/db/dbinfo/signal.bob.go +++ b/db/dbinfo/signal.bob.go @@ -78,15 +78,6 @@ var Signals = Table[ Generated: false, AutoIncr: false, }, - Title: column{ - Name: "title", - DBType: "text", - Default: "", - Comment: "", - Nullable: false, - Generated: false, - AutoIncr: false, - }, Type: column{ Name: "type_", DBType: "public.signaltype", @@ -123,6 +114,24 @@ var Signals = Table[ Generated: true, AutoIncr: false, }, + FeaturePoolFeatureID: column{ + Name: "feature_pool_feature_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + ReportID: column{ + Name: "report_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: signalIndexes{ SignalPkey: index{ @@ -201,6 +210,15 @@ var Signals = Table[ ForeignTable: "user_", ForeignColumns: []string{"id"}, }, + SignalSignalFeaturePoolFeatureIDFkey: foreignKey{ + constraint: constraint{ + Name: "signal.signal_feature_pool_feature_id_fkey", + Columns: []string{"feature_pool_feature_id"}, + Comment: "", + }, + ForeignTable: "feature_pool", + ForeignColumns: []string{"feature_id"}, + }, SignalSignalOrganizationIDFkey: foreignKey{ constraint: constraint{ Name: "signal.signal_organization_id_fkey", @@ -210,6 +228,15 @@ var Signals = Table[ ForeignTable: "organization", ForeignColumns: []string{"id"}, }, + SignalSignalReportIDFkey: foreignKey{ + constraint: constraint{ + Name: "signal.signal_report_id_fkey", + Columns: []string{"report_id"}, + Comment: "", + }, + ForeignTable: "publicreport.report", + ForeignColumns: []string{"id"}, + }, SignalSignalSiteIDFkey: foreignKey{ constraint: constraint{ Name: "signal.signal_site_id_fkey", @@ -222,6 +249,14 @@ var Signals = Table[ }, Checks: signalChecks{ + CheckExclusiveReference: check{ + constraint: constraint{ + Name: "check_exclusive_reference", + Columns: []string{"feature_pool_feature_id", "report_id"}, + Comment: "", + }, + Expression: "((feature_pool_feature_id IS NULL) OR (report_id IS NULL))", + }, ValidLocationTypes: check{ constraint: constraint{ Name: "valid_location_types", @@ -235,23 +270,24 @@ var Signals = Table[ } type signalColumns struct { - Addressed column - Addressor column - Created column - Creator column - ID column - OrganizationID column - Species column - Title column - Type column - SiteID column - Location column - LocationType column + Addressed column + Addressor column + Created column + Creator column + ID column + OrganizationID column + Species column + Type column + SiteID column + Location column + LocationType column + FeaturePoolFeatureID column + ReportID column } func (c signalColumns) AsSlice() []column { return []column{ - c.Addressed, c.Addressor, c.Created, c.Creator, c.ID, c.OrganizationID, c.Species, c.Title, c.Type, c.SiteID, c.Location, c.LocationType, + c.Addressed, c.Addressor, c.Created, c.Creator, c.ID, c.OrganizationID, c.Species, c.Type, c.SiteID, c.Location, c.LocationType, c.FeaturePoolFeatureID, c.ReportID, } } @@ -268,15 +304,17 @@ func (i signalIndexes) AsSlice() []index { } type signalForeignKeys struct { - SignalSignalAddressorFkey foreignKey - SignalSignalCreatorFkey foreignKey - SignalSignalOrganizationIDFkey foreignKey - SignalSignalSiteIDFkey foreignKey + SignalSignalAddressorFkey foreignKey + SignalSignalCreatorFkey foreignKey + SignalSignalFeaturePoolFeatureIDFkey foreignKey + SignalSignalOrganizationIDFkey foreignKey + SignalSignalReportIDFkey foreignKey + SignalSignalSiteIDFkey foreignKey } func (f signalForeignKeys) AsSlice() []foreignKey { return []foreignKey{ - f.SignalSignalAddressorFkey, f.SignalSignalCreatorFkey, f.SignalSignalOrganizationIDFkey, f.SignalSignalSiteIDFkey, + f.SignalSignalAddressorFkey, f.SignalSignalCreatorFkey, f.SignalSignalFeaturePoolFeatureIDFkey, f.SignalSignalOrganizationIDFkey, f.SignalSignalReportIDFkey, f.SignalSignalSiteIDFkey, } } @@ -287,11 +325,12 @@ func (u signalUniques) AsSlice() []constraint { } type signalChecks struct { - ValidLocationTypes check + CheckExclusiveReference check + ValidLocationTypes check } func (c signalChecks) AsSlice() []check { return []check{ - c.ValidLocationTypes, + c.CheckExclusiveReference, c.ValidLocationTypes, } } diff --git a/db/migrations/00123_signal_drop_title.sql b/db/migrations/00123_signal_drop_title.sql new file mode 100644 index 00000000..eed8b971 --- /dev/null +++ b/db/migrations/00123_signal_drop_title.sql @@ -0,0 +1,4 @@ +-- +goose up +ALTER TABLE signal DROP COLUMN title; +-- +goose down +ALTER TABLE signal ADD COLUMN title TEXT NOT NULL; diff --git a/db/migrations/00124_signal_publicreport_pool.sql b/db/migrations/00124_signal_publicreport_pool.sql new file mode 100644 index 00000000..36694c10 --- /dev/null +++ b/db/migrations/00124_signal_publicreport_pool.sql @@ -0,0 +1,12 @@ +-- +goose Up +ALTER TABLE signal ADD COLUMN feature_pool_feature_id INTEGER REFERENCES feature_pool(feature_id); +ALTER TABLE signal ADD COLUMN report_id INTEGER REFERENCES publicreport.report(id); +ALTER TABLE signal +ADD CONSTRAINT check_exclusive_reference +CHECK ( + (feature_pool_feature_id IS NULL OR report_id IS NULL) +); +-- +goose Down +ALTER TABLE signal DROP CONSTRAINT check_exclusive_reference; +ALTER TABLE signal DROP COLUMN report_id; +ALTER TABLE signal DROP COLUMN feature_pool_feature_id; diff --git a/db/models/feature_pool.bob.go b/db/models/feature_pool.bob.go index 5b1b3380..c6943055 100644 --- a/db/models/feature_pool.bob.go +++ b/db/models/feature_pool.bob.go @@ -45,8 +45,9 @@ type FeaturePoolsQuery = *psql.ViewQuery[*FeaturePool, FeaturePoolSlice] // featurePoolR is where relationships are stored. type featurePoolR struct { - Feature *Feature // feature_pool.feature_pool_feature_id_fkey - ReviewTaskPools ReviewTaskPoolSlice // review_task_pool.review_task_pool_feature_pool_fkey + Feature *Feature // feature_pool.feature_pool_feature_id_fkey + ReviewTaskPools ReviewTaskPoolSlice // review_task_pool.review_task_pool_feature_pool_fkey + FeaturePoolFeatureSignals SignalSlice // signal.signal_feature_pool_feature_id_fkey } func buildFeaturePoolColumns(alias string) featurePoolColumns { @@ -465,6 +466,30 @@ func (os FeaturePoolSlice) ReviewTaskPools(mods ...bob.Mod[*dialect.SelectQuery] )...) } +// FeaturePoolFeatureSignals starts a query for related objects on signal +func (o *FeaturePool) FeaturePoolFeatureSignals(mods ...bob.Mod[*dialect.SelectQuery]) SignalsQuery { + return Signals.Query(append(mods, + sm.Where(Signals.Columns.FeaturePoolFeatureID.EQ(psql.Arg(o.FeatureID))), + )...) +} + +func (os FeaturePoolSlice) FeaturePoolFeatureSignals(mods ...bob.Mod[*dialect.SelectQuery]) SignalsQuery { + pkFeatureID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkFeatureID = append(pkFeatureID, o.FeatureID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkFeatureID), "integer[]")), + )) + + return Signals.Query(append(mods, + sm.Where(psql.Group(Signals.Columns.FeaturePoolFeatureID).OP("IN", PKArgExpr)), + )...) +} + func attachFeaturePoolFeature0(ctx context.Context, exec bob.Executor, count int, featurePool0 *FeaturePool, feature1 *Feature) (*FeaturePool, error) { setter := &FeaturePoolSetter{ FeatureID: omit.From(feature1.ID), @@ -581,6 +606,74 @@ func (featurePool0 *FeaturePool) AttachReviewTaskPools(ctx context.Context, exec return nil } +func insertFeaturePoolFeaturePoolFeatureSignals0(ctx context.Context, exec bob.Executor, signals1 []*SignalSetter, featurePool0 *FeaturePool) (SignalSlice, error) { + for i := range signals1 { + signals1[i].FeaturePoolFeatureID = omitnull.From(featurePool0.FeatureID) + } + + ret, err := Signals.Insert(bob.ToMods(signals1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertFeaturePoolFeaturePoolFeatureSignals0: %w", err) + } + + return ret, nil +} + +func attachFeaturePoolFeaturePoolFeatureSignals0(ctx context.Context, exec bob.Executor, count int, signals1 SignalSlice, featurePool0 *FeaturePool) (SignalSlice, error) { + setter := &SignalSetter{ + FeaturePoolFeatureID: omitnull.From(featurePool0.FeatureID), + } + + err := signals1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachFeaturePoolFeaturePoolFeatureSignals0: %w", err) + } + + return signals1, nil +} + +func (featurePool0 *FeaturePool) InsertFeaturePoolFeatureSignals(ctx context.Context, exec bob.Executor, related ...*SignalSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + signals1, err := insertFeaturePoolFeaturePoolFeatureSignals0(ctx, exec, related, featurePool0) + if err != nil { + return err + } + + featurePool0.R.FeaturePoolFeatureSignals = append(featurePool0.R.FeaturePoolFeatureSignals, signals1...) + + for _, rel := range signals1 { + rel.R.FeaturePoolFeatureFeaturePool = featurePool0 + } + return nil +} + +func (featurePool0 *FeaturePool) AttachFeaturePoolFeatureSignals(ctx context.Context, exec bob.Executor, related ...*Signal) error { + if len(related) == 0 { + return nil + } + + var err error + signals1 := SignalSlice(related) + + _, err = attachFeaturePoolFeaturePoolFeatureSignals0(ctx, exec, len(related), signals1, featurePool0) + if err != nil { + return err + } + + featurePool0.R.FeaturePoolFeatureSignals = append(featurePool0.R.FeaturePoolFeatureSignals, signals1...) + + for _, rel := range related { + rel.R.FeaturePoolFeatureFeaturePool = featurePool0 + } + + return nil +} + type featurePoolWhere[Q psql.Filterable] struct { FeatureID psql.WhereMod[Q, int32] Condition psql.WhereMod[Q, enums.Poolconditiontype] @@ -633,6 +726,20 @@ func (o *FeaturePool) Preload(name string, retrieved any) error { } } return nil + case "FeaturePoolFeatureSignals": + rels, ok := retrieved.(SignalSlice) + if !ok { + return fmt.Errorf("featurePool cannot load %T as %q", retrieved, name) + } + + o.R.FeaturePoolFeatureSignals = rels + + for _, rel := range rels { + if rel != nil { + rel.R.FeaturePoolFeatureFeaturePool = o + } + } + return nil default: return fmt.Errorf("featurePool has no relationship %q", name) } @@ -661,8 +768,9 @@ func buildFeaturePoolPreloader() featurePoolPreloader { } type featurePoolThenLoader[Q orm.Loadable] struct { - Feature func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ReviewTaskPools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Feature func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ReviewTaskPools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + FeaturePoolFeatureSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildFeaturePoolThenLoader[Q orm.Loadable]() featurePoolThenLoader[Q] { @@ -672,6 +780,9 @@ func buildFeaturePoolThenLoader[Q orm.Loadable]() featurePoolThenLoader[Q] { type ReviewTaskPoolsLoadInterface interface { LoadReviewTaskPools(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type FeaturePoolFeatureSignalsLoadInterface interface { + LoadFeaturePoolFeatureSignals(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return featurePoolThenLoader[Q]{ Feature: thenLoadBuilder[Q]( @@ -686,6 +797,12 @@ func buildFeaturePoolThenLoader[Q orm.Loadable]() featurePoolThenLoader[Q] { return retrieved.LoadReviewTaskPools(ctx, exec, mods...) }, ), + FeaturePoolFeatureSignals: thenLoadBuilder[Q]( + "FeaturePoolFeatureSignals", + func(ctx context.Context, exec bob.Executor, retrieved FeaturePoolFeatureSignalsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadFeaturePoolFeatureSignals(ctx, exec, mods...) + }, + ), } } @@ -801,3 +918,67 @@ func (os FeaturePoolSlice) LoadReviewTaskPools(ctx context.Context, exec bob.Exe return nil } + +// LoadFeaturePoolFeatureSignals loads the featurePool's FeaturePoolFeatureSignals into the .R struct +func (o *FeaturePool) LoadFeaturePoolFeatureSignals(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.FeaturePoolFeatureSignals = nil + + related, err := o.FeaturePoolFeatureSignals(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.FeaturePoolFeatureFeaturePool = o + } + + o.R.FeaturePoolFeatureSignals = related + return nil +} + +// LoadFeaturePoolFeatureSignals loads the featurePool's FeaturePoolFeatureSignals into the .R struct +func (os FeaturePoolSlice) LoadFeaturePoolFeatureSignals(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + signals, err := os.FeaturePoolFeatureSignals(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.FeaturePoolFeatureSignals = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range signals { + + if !rel.FeaturePoolFeatureID.IsValue() { + continue + } + if !(rel.FeaturePoolFeatureID.IsValue() && o.FeatureID == rel.FeaturePoolFeatureID.MustGet()) { + continue + } + + rel.R.FeaturePoolFeatureFeaturePool = o + + o.R.FeaturePoolFeatureSignals = append(o.R.FeaturePoolFeatureSignals, rel) + } + } + + return nil +} diff --git a/db/models/publicreport.report.bob.go b/db/models/publicreport.report.bob.go index 4e9321e7..7737fcfd 100644 --- a/db/models/publicreport.report.bob.go +++ b/db/models/publicreport.report.bob.go @@ -81,6 +81,7 @@ type publicreportReportR struct { ReportLogs PublicreportReportLogSlice // publicreport.report_log.report_log_report_id_fkey Water *PublicreportWater // publicreport.water.water_report_id_fkey ReportTexts ReportTextSlice // report_text.report_text_report_id_fkey + Signals SignalSlice // signal.signal_report_id_fkey } func buildPublicreportReportColumns(alias string) publicreportReportColumns { @@ -1186,6 +1187,30 @@ func (os PublicreportReportSlice) ReportTexts(mods ...bob.Mod[*dialect.SelectQue )...) } +// Signals starts a query for related objects on signal +func (o *PublicreportReport) Signals(mods ...bob.Mod[*dialect.SelectQuery]) SignalsQuery { + return Signals.Query(append(mods, + sm.Where(Signals.Columns.ReportID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os PublicreportReportSlice) Signals(mods ...bob.Mod[*dialect.SelectQuery]) SignalsQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return Signals.Query(append(mods, + sm.Where(psql.Group(Signals.Columns.ReportID).OP("IN", PKArgExpr)), + )...) +} + func insertPublicreportReportTextJobs0(ctx context.Context, exec bob.Executor, commsTextJobs1 []*CommsTextJobSetter, publicreportReport0 *PublicreportReport) (CommsTextJobSlice, error) { for i := range commsTextJobs1 { commsTextJobs1[i].ReportID = omitnull.From(publicreportReport0.ID) @@ -1843,6 +1868,74 @@ func (publicreportReport0 *PublicreportReport) AttachReportTexts(ctx context.Con return nil } +func insertPublicreportReportSignals0(ctx context.Context, exec bob.Executor, signals1 []*SignalSetter, publicreportReport0 *PublicreportReport) (SignalSlice, error) { + for i := range signals1 { + signals1[i].ReportID = omitnull.From(publicreportReport0.ID) + } + + ret, err := Signals.Insert(bob.ToMods(signals1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertPublicreportReportSignals0: %w", err) + } + + return ret, nil +} + +func attachPublicreportReportSignals0(ctx context.Context, exec bob.Executor, count int, signals1 SignalSlice, publicreportReport0 *PublicreportReport) (SignalSlice, error) { + setter := &SignalSetter{ + ReportID: omitnull.From(publicreportReport0.ID), + } + + err := signals1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportReportSignals0: %w", err) + } + + return signals1, nil +} + +func (publicreportReport0 *PublicreportReport) InsertSignals(ctx context.Context, exec bob.Executor, related ...*SignalSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + signals1, err := insertPublicreportReportSignals0(ctx, exec, related, publicreportReport0) + if err != nil { + return err + } + + publicreportReport0.R.Signals = append(publicreportReport0.R.Signals, signals1...) + + for _, rel := range signals1 { + rel.R.Report = publicreportReport0 + } + return nil +} + +func (publicreportReport0 *PublicreportReport) AttachSignals(ctx context.Context, exec bob.Executor, related ...*Signal) error { + if len(related) == 0 { + return nil + } + + var err error + signals1 := SignalSlice(related) + + _, err = attachPublicreportReportSignals0(ctx, exec, len(related), signals1, publicreportReport0) + if err != nil { + return err + } + + publicreportReport0.R.Signals = append(publicreportReport0.R.Signals, signals1...) + + for _, rel := range related { + rel.R.Report = publicreportReport0 + } + + return nil +} + type publicreportReportWhere[Q psql.Filterable] struct { AddressRaw psql.WhereMod[Q, string] AddressNumber psql.WhereMod[Q, string] @@ -2053,6 +2146,20 @@ func (o *PublicreportReport) Preload(name string, retrieved any) error { o.R.ReportTexts = rels + for _, rel := range rels { + if rel != nil { + rel.R.Report = o + } + } + return nil + case "Signals": + rels, ok := retrieved.(SignalSlice) + if !ok { + return fmt.Errorf("publicreportReport cannot load %T as %q", retrieved, name) + } + + o.R.Signals = rels + for _, rel := range rels { if rel != nil { rel.R.Report = o @@ -2154,6 +2261,7 @@ type publicreportReportThenLoader[Q orm.Loadable] struct { ReportLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Water func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ReportTexts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Signals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportReportThenLoader[Q orm.Loadable]() publicreportReportThenLoader[Q] { @@ -2190,6 +2298,9 @@ func buildPublicreportReportThenLoader[Q orm.Loadable]() publicreportReportThenL type ReportTextsLoadInterface interface { LoadReportTexts(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type SignalsLoadInterface interface { + LoadSignals(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return publicreportReportThenLoader[Q]{ TextJobs: thenLoadBuilder[Q]( @@ -2258,6 +2369,12 @@ func buildPublicreportReportThenLoader[Q orm.Loadable]() publicreportReportThenL return retrieved.LoadReportTexts(ctx, exec, mods...) }, ), + Signals: thenLoadBuilder[Q]( + "Signals", + func(ctx context.Context, exec bob.Executor, retrieved SignalsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSignals(ctx, exec, mods...) + }, + ), } } @@ -2915,3 +3032,67 @@ func (os PublicreportReportSlice) LoadReportTexts(ctx context.Context, exec bob. return nil } + +// LoadSignals loads the publicreportReport's Signals into the .R struct +func (o *PublicreportReport) LoadSignals(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Signals = nil + + related, err := o.Signals(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Report = o + } + + o.R.Signals = related + return nil +} + +// LoadSignals loads the publicreportReport's Signals into the .R struct +func (os PublicreportReportSlice) LoadSignals(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + signals, err := os.Signals(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.Signals = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range signals { + + if !rel.ReportID.IsValue() { + continue + } + if !(rel.ReportID.IsValue() && o.ID == rel.ReportID.MustGet()) { + continue + } + + rel.R.Report = o + + o.R.Signals = append(o.R.Signals, rel) + } + } + + return nil +} diff --git a/db/models/signal.bob.go b/db/models/signal.bob.go index 178c9cea..389b274f 100644 --- a/db/models/signal.bob.go +++ b/db/models/signal.bob.go @@ -26,18 +26,19 @@ import ( // Signal is an object representing the database table. type Signal struct { - Addressed null.Val[time.Time] `db:"addressed" ` - Addressor null.Val[int32] `db:"addressor" ` - Created time.Time `db:"created" ` - Creator int32 `db:"creator" ` - ID int32 `db:"id,pk" ` - OrganizationID int32 `db:"organization_id" ` - Species null.Val[enums.Mosquitospecies] `db:"species" ` - Title string `db:"title" ` - Type enums.Signaltype `db:"type_" ` - SiteID null.Val[int32] `db:"site_id" ` - Location string `db:"location" ` - LocationType null.Val[string] `db:"location_type,generated" ` + Addressed null.Val[time.Time] `db:"addressed" ` + Addressor null.Val[int32] `db:"addressor" ` + Created time.Time `db:"created" ` + Creator int32 `db:"creator" ` + ID int32 `db:"id,pk" ` + OrganizationID int32 `db:"organization_id" ` + Species null.Val[enums.Mosquitospecies] `db:"species" ` + Type enums.Signaltype `db:"type_" ` + SiteID null.Val[int32] `db:"site_id" ` + Location string `db:"location" ` + LocationType null.Val[string] `db:"location_type,generated" ` + FeaturePoolFeatureID null.Val[int32] `db:"feature_pool_feature_id" ` + ReportID null.Val[int32] `db:"report_id" ` R signalR `db:"-" ` } @@ -54,48 +55,52 @@ type SignalsQuery = *psql.ViewQuery[*Signal, SignalSlice] // signalR is where relationships are stored. type signalR struct { - AddressorUser *User // signal.signal_addressor_fkey - CreatorUser *User // signal.signal_creator_fkey - Organization *Organization // signal.signal_organization_id_fkey - Site *Site // signal.signal_site_id_fkey + AddressorUser *User // signal.signal_addressor_fkey + CreatorUser *User // signal.signal_creator_fkey + FeaturePoolFeatureFeaturePool *FeaturePool // signal.signal_feature_pool_feature_id_fkey + Organization *Organization // signal.signal_organization_id_fkey + Report *PublicreportReport // signal.signal_report_id_fkey + Site *Site // signal.signal_site_id_fkey } func buildSignalColumns(alias string) signalColumns { return signalColumns{ ColumnsExpr: expr.NewColumnsExpr( - "addressed", "addressor", "created", "creator", "id", "organization_id", "species", "title", "type_", "site_id", "location", "location_type", + "addressed", "addressor", "created", "creator", "id", "organization_id", "species", "type_", "site_id", "location", "location_type", "feature_pool_feature_id", "report_id", ).WithParent("signal"), - tableAlias: alias, - Addressed: psql.Quote(alias, "addressed"), - Addressor: psql.Quote(alias, "addressor"), - Created: psql.Quote(alias, "created"), - Creator: psql.Quote(alias, "creator"), - ID: psql.Quote(alias, "id"), - OrganizationID: psql.Quote(alias, "organization_id"), - Species: psql.Quote(alias, "species"), - Title: psql.Quote(alias, "title"), - Type: psql.Quote(alias, "type_"), - SiteID: psql.Quote(alias, "site_id"), - Location: psql.Quote(alias, "location"), - LocationType: psql.Quote(alias, "location_type"), + tableAlias: alias, + Addressed: psql.Quote(alias, "addressed"), + Addressor: psql.Quote(alias, "addressor"), + Created: psql.Quote(alias, "created"), + Creator: psql.Quote(alias, "creator"), + ID: psql.Quote(alias, "id"), + OrganizationID: psql.Quote(alias, "organization_id"), + Species: psql.Quote(alias, "species"), + Type: psql.Quote(alias, "type_"), + SiteID: psql.Quote(alias, "site_id"), + Location: psql.Quote(alias, "location"), + LocationType: psql.Quote(alias, "location_type"), + FeaturePoolFeatureID: psql.Quote(alias, "feature_pool_feature_id"), + ReportID: psql.Quote(alias, "report_id"), } } type signalColumns struct { expr.ColumnsExpr - tableAlias string - Addressed psql.Expression - Addressor psql.Expression - Created psql.Expression - Creator psql.Expression - ID psql.Expression - OrganizationID psql.Expression - Species psql.Expression - Title psql.Expression - Type psql.Expression - SiteID psql.Expression - Location psql.Expression - LocationType psql.Expression + tableAlias string + Addressed psql.Expression + Addressor psql.Expression + Created psql.Expression + Creator psql.Expression + ID psql.Expression + OrganizationID psql.Expression + Species psql.Expression + Type psql.Expression + SiteID psql.Expression + Location psql.Expression + LocationType psql.Expression + FeaturePoolFeatureID psql.Expression + ReportID psql.Expression } func (c signalColumns) Alias() string { @@ -110,21 +115,22 @@ func (signalColumns) AliasedAs(alias string) signalColumns { // All values are optional, and do not have to be set // Generated columns are not included type SignalSetter struct { - Addressed omitnull.Val[time.Time] `db:"addressed" ` - Addressor omitnull.Val[int32] `db:"addressor" ` - Created omit.Val[time.Time] `db:"created" ` - Creator omit.Val[int32] `db:"creator" ` - ID omit.Val[int32] `db:"id,pk" ` - OrganizationID omit.Val[int32] `db:"organization_id" ` - Species omitnull.Val[enums.Mosquitospecies] `db:"species" ` - Title omit.Val[string] `db:"title" ` - Type omit.Val[enums.Signaltype] `db:"type_" ` - SiteID omitnull.Val[int32] `db:"site_id" ` - Location omit.Val[string] `db:"location" ` + Addressed omitnull.Val[time.Time] `db:"addressed" ` + Addressor omitnull.Val[int32] `db:"addressor" ` + Created omit.Val[time.Time] `db:"created" ` + Creator omit.Val[int32] `db:"creator" ` + ID omit.Val[int32] `db:"id,pk" ` + OrganizationID omit.Val[int32] `db:"organization_id" ` + Species omitnull.Val[enums.Mosquitospecies] `db:"species" ` + Type omit.Val[enums.Signaltype] `db:"type_" ` + SiteID omitnull.Val[int32] `db:"site_id" ` + Location omit.Val[string] `db:"location" ` + FeaturePoolFeatureID omitnull.Val[int32] `db:"feature_pool_feature_id" ` + ReportID omitnull.Val[int32] `db:"report_id" ` } func (s SignalSetter) SetColumns() []string { - vals := make([]string, 0, 11) + vals := make([]string, 0, 12) if !s.Addressed.IsUnset() { vals = append(vals, "addressed") } @@ -146,9 +152,6 @@ func (s SignalSetter) SetColumns() []string { if !s.Species.IsUnset() { vals = append(vals, "species") } - if s.Title.IsValue() { - vals = append(vals, "title") - } if s.Type.IsValue() { vals = append(vals, "type_") } @@ -158,6 +161,12 @@ func (s SignalSetter) SetColumns() []string { if s.Location.IsValue() { vals = append(vals, "location") } + if !s.FeaturePoolFeatureID.IsUnset() { + vals = append(vals, "feature_pool_feature_id") + } + if !s.ReportID.IsUnset() { + vals = append(vals, "report_id") + } return vals } @@ -183,9 +192,6 @@ func (s SignalSetter) Overwrite(t *Signal) { if !s.Species.IsUnset() { t.Species = s.Species.MustGetNull() } - if s.Title.IsValue() { - t.Title = s.Title.MustGet() - } if s.Type.IsValue() { t.Type = s.Type.MustGet() } @@ -195,6 +201,12 @@ func (s SignalSetter) Overwrite(t *Signal) { if s.Location.IsValue() { t.Location = s.Location.MustGet() } + if !s.FeaturePoolFeatureID.IsUnset() { + t.FeaturePoolFeatureID = s.FeaturePoolFeatureID.MustGetNull() + } + if !s.ReportID.IsUnset() { + t.ReportID = s.ReportID.MustGetNull() + } } func (s *SignalSetter) Apply(q *dialect.InsertQuery) { @@ -203,7 +215,7 @@ func (s *SignalSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 11) + vals := make([]bob.Expression, 12) if !s.Addressed.IsUnset() { vals[0] = psql.Arg(s.Addressed.MustGetNull()) } else { @@ -246,30 +258,36 @@ func (s *SignalSetter) Apply(q *dialect.InsertQuery) { vals[6] = psql.Raw("DEFAULT") } - if s.Title.IsValue() { - vals[7] = psql.Arg(s.Title.MustGet()) + if s.Type.IsValue() { + vals[7] = psql.Arg(s.Type.MustGet()) } else { vals[7] = psql.Raw("DEFAULT") } - if s.Type.IsValue() { - vals[8] = psql.Arg(s.Type.MustGet()) + if !s.SiteID.IsUnset() { + vals[8] = psql.Arg(s.SiteID.MustGetNull()) } else { vals[8] = psql.Raw("DEFAULT") } - if !s.SiteID.IsUnset() { - vals[9] = psql.Arg(s.SiteID.MustGetNull()) + if s.Location.IsValue() { + vals[9] = psql.Arg(s.Location.MustGet()) } else { vals[9] = psql.Raw("DEFAULT") } - if s.Location.IsValue() { - vals[10] = psql.Arg(s.Location.MustGet()) + if !s.FeaturePoolFeatureID.IsUnset() { + vals[10] = psql.Arg(s.FeaturePoolFeatureID.MustGetNull()) } else { vals[10] = psql.Raw("DEFAULT") } + if !s.ReportID.IsUnset() { + vals[11] = psql.Arg(s.ReportID.MustGetNull()) + } else { + vals[11] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -279,7 +297,7 @@ func (s SignalSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s SignalSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 11) + exprs := make([]bob.Expression, 0, 12) if !s.Addressed.IsUnset() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -330,13 +348,6 @@ func (s SignalSetter) Expressions(prefix ...string) []bob.Expression { }}) } - if s.Title.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "title")...), - psql.Arg(s.Title), - }}) - } - if s.Type.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "type_")...), @@ -358,6 +369,20 @@ func (s SignalSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.FeaturePoolFeatureID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "feature_pool_feature_id")...), + psql.Arg(s.FeaturePoolFeatureID), + }}) + } + + if !s.ReportID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "report_id")...), + psql.Arg(s.ReportID), + }}) + } + return exprs } @@ -632,6 +657,30 @@ func (os SignalSlice) CreatorUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQu )...) } +// FeaturePoolFeatureFeaturePool starts a query for related objects on feature_pool +func (o *Signal) FeaturePoolFeatureFeaturePool(mods ...bob.Mod[*dialect.SelectQuery]) FeaturePoolsQuery { + return FeaturePools.Query(append(mods, + sm.Where(FeaturePools.Columns.FeatureID.EQ(psql.Arg(o.FeaturePoolFeatureID))), + )...) +} + +func (os SignalSlice) FeaturePoolFeatureFeaturePool(mods ...bob.Mod[*dialect.SelectQuery]) FeaturePoolsQuery { + pkFeaturePoolFeatureID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkFeaturePoolFeatureID = append(pkFeaturePoolFeatureID, o.FeaturePoolFeatureID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkFeaturePoolFeatureID), "integer[]")), + )) + + return FeaturePools.Query(append(mods, + sm.Where(psql.Group(FeaturePools.Columns.FeatureID).OP("IN", PKArgExpr)), + )...) +} + // Organization starts a query for related objects on organization func (o *Signal) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { return Organizations.Query(append(mods, @@ -656,6 +705,30 @@ func (os SignalSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) Organi )...) } +// Report starts a query for related objects on publicreport.report +func (o *Signal) Report(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportReportsQuery { + return PublicreportReports.Query(append(mods, + sm.Where(PublicreportReports.Columns.ID.EQ(psql.Arg(o.ReportID))), + )...) +} + +func (os SignalSlice) Report(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportReportsQuery { + pkReportID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkReportID = append(pkReportID, o.ReportID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkReportID), "integer[]")), + )) + + return PublicreportReports.Query(append(mods, + sm.Where(psql.Group(PublicreportReports.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + // Site starts a query for related objects on site func (o *Signal) Site(mods ...bob.Mod[*dialect.SelectQuery]) SitesQuery { return Sites.Query(append(mods, @@ -776,6 +849,54 @@ func (signal0 *Signal) AttachCreatorUser(ctx context.Context, exec bob.Executor, return nil } +func attachSignalFeaturePoolFeatureFeaturePool0(ctx context.Context, exec bob.Executor, count int, signal0 *Signal, featurePool1 *FeaturePool) (*Signal, error) { + setter := &SignalSetter{ + FeaturePoolFeatureID: omitnull.From(featurePool1.FeatureID), + } + + err := signal0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachSignalFeaturePoolFeatureFeaturePool0: %w", err) + } + + return signal0, nil +} + +func (signal0 *Signal) InsertFeaturePoolFeatureFeaturePool(ctx context.Context, exec bob.Executor, related *FeaturePoolSetter) error { + var err error + + featurePool1, err := FeaturePools.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachSignalFeaturePoolFeatureFeaturePool0(ctx, exec, 1, signal0, featurePool1) + if err != nil { + return err + } + + signal0.R.FeaturePoolFeatureFeaturePool = featurePool1 + + featurePool1.R.FeaturePoolFeatureSignals = append(featurePool1.R.FeaturePoolFeatureSignals, signal0) + + return nil +} + +func (signal0 *Signal) AttachFeaturePoolFeatureFeaturePool(ctx context.Context, exec bob.Executor, featurePool1 *FeaturePool) error { + var err error + + _, err = attachSignalFeaturePoolFeatureFeaturePool0(ctx, exec, 1, signal0, featurePool1) + if err != nil { + return err + } + + signal0.R.FeaturePoolFeatureFeaturePool = featurePool1 + + featurePool1.R.FeaturePoolFeatureSignals = append(featurePool1.R.FeaturePoolFeatureSignals, signal0) + + return nil +} + func attachSignalOrganization0(ctx context.Context, exec bob.Executor, count int, signal0 *Signal, organization1 *Organization) (*Signal, error) { setter := &SignalSetter{ OrganizationID: omit.From(organization1.ID), @@ -824,6 +945,54 @@ func (signal0 *Signal) AttachOrganization(ctx context.Context, exec bob.Executor return nil } +func attachSignalReport0(ctx context.Context, exec bob.Executor, count int, signal0 *Signal, publicreportReport1 *PublicreportReport) (*Signal, error) { + setter := &SignalSetter{ + ReportID: omitnull.From(publicreportReport1.ID), + } + + err := signal0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachSignalReport0: %w", err) + } + + return signal0, nil +} + +func (signal0 *Signal) InsertReport(ctx context.Context, exec bob.Executor, related *PublicreportReportSetter) error { + var err error + + publicreportReport1, err := PublicreportReports.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachSignalReport0(ctx, exec, 1, signal0, publicreportReport1) + if err != nil { + return err + } + + signal0.R.Report = publicreportReport1 + + publicreportReport1.R.Signals = append(publicreportReport1.R.Signals, signal0) + + return nil +} + +func (signal0 *Signal) AttachReport(ctx context.Context, exec bob.Executor, publicreportReport1 *PublicreportReport) error { + var err error + + _, err = attachSignalReport0(ctx, exec, 1, signal0, publicreportReport1) + if err != nil { + return err + } + + signal0.R.Report = publicreportReport1 + + publicreportReport1.R.Signals = append(publicreportReport1.R.Signals, signal0) + + return nil +} + func attachSignalSite0(ctx context.Context, exec bob.Executor, count int, signal0 *Signal, site1 *Site) (*Signal, error) { setter := &SignalSetter{ SiteID: omitnull.From(site1.ID), @@ -873,18 +1042,19 @@ func (signal0 *Signal) AttachSite(ctx context.Context, exec bob.Executor, site1 } type signalWhere[Q psql.Filterable] struct { - Addressed psql.WhereNullMod[Q, time.Time] - Addressor psql.WhereNullMod[Q, int32] - Created psql.WhereMod[Q, time.Time] - Creator psql.WhereMod[Q, int32] - ID psql.WhereMod[Q, int32] - OrganizationID psql.WhereMod[Q, int32] - Species psql.WhereNullMod[Q, enums.Mosquitospecies] - Title psql.WhereMod[Q, string] - Type psql.WhereMod[Q, enums.Signaltype] - SiteID psql.WhereNullMod[Q, int32] - Location psql.WhereMod[Q, string] - LocationType psql.WhereNullMod[Q, string] + Addressed psql.WhereNullMod[Q, time.Time] + Addressor psql.WhereNullMod[Q, int32] + Created psql.WhereMod[Q, time.Time] + Creator psql.WhereMod[Q, int32] + ID psql.WhereMod[Q, int32] + OrganizationID psql.WhereMod[Q, int32] + Species psql.WhereNullMod[Q, enums.Mosquitospecies] + Type psql.WhereMod[Q, enums.Signaltype] + SiteID psql.WhereNullMod[Q, int32] + Location psql.WhereMod[Q, string] + LocationType psql.WhereNullMod[Q, string] + FeaturePoolFeatureID psql.WhereNullMod[Q, int32] + ReportID psql.WhereNullMod[Q, int32] } func (signalWhere[Q]) AliasedAs(alias string) signalWhere[Q] { @@ -893,18 +1063,19 @@ func (signalWhere[Q]) AliasedAs(alias string) signalWhere[Q] { func buildSignalWhere[Q psql.Filterable](cols signalColumns) signalWhere[Q] { return signalWhere[Q]{ - Addressed: psql.WhereNull[Q, time.Time](cols.Addressed), - Addressor: psql.WhereNull[Q, int32](cols.Addressor), - Created: psql.Where[Q, time.Time](cols.Created), - Creator: psql.Where[Q, int32](cols.Creator), - ID: psql.Where[Q, int32](cols.ID), - OrganizationID: psql.Where[Q, int32](cols.OrganizationID), - Species: psql.WhereNull[Q, enums.Mosquitospecies](cols.Species), - Title: psql.Where[Q, string](cols.Title), - Type: psql.Where[Q, enums.Signaltype](cols.Type), - SiteID: psql.WhereNull[Q, int32](cols.SiteID), - Location: psql.Where[Q, string](cols.Location), - LocationType: psql.WhereNull[Q, string](cols.LocationType), + Addressed: psql.WhereNull[Q, time.Time](cols.Addressed), + Addressor: psql.WhereNull[Q, int32](cols.Addressor), + Created: psql.Where[Q, time.Time](cols.Created), + Creator: psql.Where[Q, int32](cols.Creator), + ID: psql.Where[Q, int32](cols.ID), + OrganizationID: psql.Where[Q, int32](cols.OrganizationID), + Species: psql.WhereNull[Q, enums.Mosquitospecies](cols.Species), + Type: psql.Where[Q, enums.Signaltype](cols.Type), + SiteID: psql.WhereNull[Q, int32](cols.SiteID), + Location: psql.Where[Q, string](cols.Location), + LocationType: psql.WhereNull[Q, string](cols.LocationType), + FeaturePoolFeatureID: psql.WhereNull[Q, int32](cols.FeaturePoolFeatureID), + ReportID: psql.WhereNull[Q, int32](cols.ReportID), } } @@ -938,6 +1109,18 @@ func (o *Signal) Preload(name string, retrieved any) error { rel.R.CreatorSignals = SignalSlice{o} } return nil + case "FeaturePoolFeatureFeaturePool": + rel, ok := retrieved.(*FeaturePool) + if !ok { + return fmt.Errorf("signal cannot load %T as %q", retrieved, name) + } + + o.R.FeaturePoolFeatureFeaturePool = rel + + if rel != nil { + rel.R.FeaturePoolFeatureSignals = SignalSlice{o} + } + return nil case "Organization": rel, ok := retrieved.(*Organization) if !ok { @@ -946,6 +1129,18 @@ func (o *Signal) Preload(name string, retrieved any) error { o.R.Organization = rel + if rel != nil { + rel.R.Signals = SignalSlice{o} + } + return nil + case "Report": + rel, ok := retrieved.(*PublicreportReport) + if !ok { + return fmt.Errorf("signal cannot load %T as %q", retrieved, name) + } + + o.R.Report = rel + if rel != nil { rel.R.Signals = SignalSlice{o} } @@ -968,10 +1163,12 @@ func (o *Signal) Preload(name string, retrieved any) error { } type signalPreloader struct { - AddressorUser func(...psql.PreloadOption) psql.Preloader - CreatorUser func(...psql.PreloadOption) psql.Preloader - Organization func(...psql.PreloadOption) psql.Preloader - Site func(...psql.PreloadOption) psql.Preloader + AddressorUser func(...psql.PreloadOption) psql.Preloader + CreatorUser func(...psql.PreloadOption) psql.Preloader + FeaturePoolFeatureFeaturePool func(...psql.PreloadOption) psql.Preloader + Organization func(...psql.PreloadOption) psql.Preloader + Report func(...psql.PreloadOption) psql.Preloader + Site func(...psql.PreloadOption) psql.Preloader } func buildSignalPreloader() signalPreloader { @@ -1002,6 +1199,19 @@ func buildSignalPreloader() signalPreloader { }, }, Users.Columns.Names(), opts...) }, + FeaturePoolFeatureFeaturePool: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*FeaturePool, FeaturePoolSlice](psql.PreloadRel{ + Name: "FeaturePoolFeatureFeaturePool", + Sides: []psql.PreloadSide{ + { + From: Signals, + To: FeaturePools, + FromColumns: []string{"feature_pool_feature_id"}, + ToColumns: []string{"feature_id"}, + }, + }, + }, FeaturePools.Columns.Names(), opts...) + }, Organization: func(opts ...psql.PreloadOption) psql.Preloader { return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ Name: "Organization", @@ -1015,6 +1225,19 @@ func buildSignalPreloader() signalPreloader { }, }, Organizations.Columns.Names(), opts...) }, + Report: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*PublicreportReport, PublicreportReportSlice](psql.PreloadRel{ + Name: "Report", + Sides: []psql.PreloadSide{ + { + From: Signals, + To: PublicreportReports, + FromColumns: []string{"report_id"}, + ToColumns: []string{"id"}, + }, + }, + }, PublicreportReports.Columns.Names(), opts...) + }, Site: func(opts ...psql.PreloadOption) psql.Preloader { return psql.Preload[*Site, SiteSlice](psql.PreloadRel{ Name: "Site", @@ -1032,10 +1255,12 @@ func buildSignalPreloader() signalPreloader { } type signalThenLoader[Q orm.Loadable] struct { - AddressorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Site func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + AddressorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + FeaturePoolFeatureFeaturePool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Report func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Site func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildSignalThenLoader[Q orm.Loadable]() signalThenLoader[Q] { @@ -1045,9 +1270,15 @@ func buildSignalThenLoader[Q orm.Loadable]() signalThenLoader[Q] { type CreatorUserLoadInterface interface { LoadCreatorUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type FeaturePoolFeatureFeaturePoolLoadInterface interface { + LoadFeaturePoolFeatureFeaturePool(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type OrganizationLoadInterface interface { LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type ReportLoadInterface interface { + LoadReport(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type SiteLoadInterface interface { LoadSite(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -1065,12 +1296,24 @@ func buildSignalThenLoader[Q orm.Loadable]() signalThenLoader[Q] { return retrieved.LoadCreatorUser(ctx, exec, mods...) }, ), + FeaturePoolFeatureFeaturePool: thenLoadBuilder[Q]( + "FeaturePoolFeatureFeaturePool", + func(ctx context.Context, exec bob.Executor, retrieved FeaturePoolFeatureFeaturePoolLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadFeaturePoolFeatureFeaturePool(ctx, exec, mods...) + }, + ), Organization: thenLoadBuilder[Q]( "Organization", func(ctx context.Context, exec bob.Executor, retrieved OrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { return retrieved.LoadOrganization(ctx, exec, mods...) }, ), + Report: thenLoadBuilder[Q]( + "Report", + func(ctx context.Context, exec bob.Executor, retrieved ReportLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadReport(ctx, exec, mods...) + }, + ), Site: thenLoadBuilder[Q]( "Site", func(ctx context.Context, exec bob.Executor, retrieved SiteLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -1187,6 +1430,61 @@ func (os SignalSlice) LoadCreatorUser(ctx context.Context, exec bob.Executor, mo return nil } +// LoadFeaturePoolFeatureFeaturePool loads the signal's FeaturePoolFeatureFeaturePool into the .R struct +func (o *Signal) LoadFeaturePoolFeatureFeaturePool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.FeaturePoolFeatureFeaturePool = nil + + related, err := o.FeaturePoolFeatureFeaturePool(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.FeaturePoolFeatureSignals = SignalSlice{o} + + o.R.FeaturePoolFeatureFeaturePool = related + return nil +} + +// LoadFeaturePoolFeatureFeaturePool loads the signal's FeaturePoolFeatureFeaturePool into the .R struct +func (os SignalSlice) LoadFeaturePoolFeatureFeaturePool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + featurePools, err := os.FeaturePoolFeatureFeaturePool(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range featurePools { + if !o.FeaturePoolFeatureID.IsValue() { + continue + } + + if !(o.FeaturePoolFeatureID.IsValue() && o.FeaturePoolFeatureID.MustGet() == rel.FeatureID) { + continue + } + + rel.R.FeaturePoolFeatureSignals = append(rel.R.FeaturePoolFeatureSignals, o) + + o.R.FeaturePoolFeatureFeaturePool = rel + break + } + } + + return nil +} + // LoadOrganization loads the signal's Organization into the .R struct func (o *Signal) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -1239,6 +1537,61 @@ func (os SignalSlice) LoadOrganization(ctx context.Context, exec bob.Executor, m return nil } +// LoadReport loads the signal's Report into the .R struct +func (o *Signal) LoadReport(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Report = nil + + related, err := o.Report(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.Signals = SignalSlice{o} + + o.R.Report = related + return nil +} + +// LoadReport loads the signal's Report into the .R struct +func (os SignalSlice) LoadReport(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportReports, err := os.Report(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportReports { + if !o.ReportID.IsValue() { + continue + } + + if !(o.ReportID.IsValue() && o.ReportID.MustGet() == rel.ID) { + continue + } + + rel.R.Signals = append(rel.R.Signals, o) + + o.R.Report = rel + break + } + } + + return nil +} + // LoadSite loads the signal's Site into the .R struct func (o *Signal) LoadSite(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { diff --git a/platform/pool.go b/platform/pool.go index bf8756b0..a28a65f7 100644 --- a/platform/pool.go +++ b/platform/pool.go @@ -6,12 +6,21 @@ import ( "fmt" "time" + "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" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/platform/types" + //"github.com/rs/zerolog/log" + "github.com/stephenafamo/scan" ) +type Pool struct { + Condition string `db:"condition" json:"condition"` + ID int32 `db:"id" json:"-"` +} type UploadPoolDetail struct { CountExisting int CountNew int @@ -149,3 +158,19 @@ func errorsByLine(ctx context.Context, file *models.FileuploadFile) ([]UploadPoo } return file_errors, errors_by_line, nil } +func poolList(ctx context.Context, org_id int32, pool_ids []int32) ([]*Pool, error) { + pools, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( + sm.Columns( + "condition", + "feature_id AS id", + ), + sm.From(psql.Quote("feature_pool")), + sm.Where( + models.FeaturePools.Columns.FeatureID.EQ(psql.Any(pool_ids)), + ), + ), scan.StructMapper[*Pool]()) + if err != nil { + return nil, fmt.Errorf("query feature_pool: %w", err) + } + return pools, nil +} diff --git a/platform/publicreport/log.go b/platform/publicreport/log.go index b32457d0..86ac42f5 100644 --- a/platform/publicreport/log.go +++ b/platform/publicreport/log.go @@ -3,29 +3,20 @@ package publicreport import ( "context" "fmt" - "time" "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" + "github.com/Gleipnir-Technology/nidus-sync/platform/types" "github.com/rs/zerolog/log" "github.com/stephenafamo/scan" ) -type LogEntry struct { - Created time.Time `db:"created" json:"created"` - ID int32 `db:"id" json:"-"` - Message string `db:"message" json:"message"` - ReportID int32 `db:"report_id" json:"-"` - Type string `db:"type_" json:"type"` - UserID *int32 `db:"user_id" json:"user_id"` -} - -func logEntriesByReportID(ctx context.Context, report_ids []int32) (map[int32][]LogEntry, error) { - results := make(map[int32][]LogEntry, len(report_ids)) +func logEntriesByReportID(ctx context.Context, report_ids []int32) (map[int32][]types.LogEntry, error) { + results := make(map[int32][]types.LogEntry, len(report_ids)) for _, report_id := range report_ids { - results[report_id] = make([]LogEntry, 0) + results[report_id] = make([]types.LogEntry, 0) } rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( @@ -48,7 +39,7 @@ func logEntriesByReportID(ctx context.Context, report_ids []int32) (map[int32][] ), sm.Where(psql.Quote("l", "report_id").EQ(psql.Any(report_ids))), sm.OrderBy(psql.Quote("l", "created")), - ), scan.StructMapper[LogEntry]()) + ), scan.StructMapper[types.LogEntry]()) if err != nil { return results, fmt.Errorf("query created: %w", err) } diff --git a/platform/publicreport/nuisance.go b/platform/publicreport/nuisance.go index 6ef7d498..543e1b7f 100644 --- a/platform/publicreport/nuisance.go +++ b/platform/publicreport/nuisance.go @@ -9,31 +9,13 @@ import ( "github.com/Gleipnir-Technology/bob/dialect/psql/sm" //"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/platform/types" //"github.com/google/uuid" //"github.com/rs/zerolog/log" "github.com/stephenafamo/scan" ) -type Nuisance struct { - AdditionalInfo string `db:"additional_info" json:"additional_info"` - Duration string `db:"duration" json:"duration"` - IsLocationBackyard bool `db:"is_location_backyard" json:"is_location_backyard"` - IsLocationFrontyard bool `db:"is_location_frontyard" json:"is_location_frontyard"` - IsLocationGarden bool `db:"is_location_garden" json:"is_location_garden"` - IsLocationOther bool `db:"is_location_other" json:"is_location_other"` - IsLocationPool bool `db:"is_location_pool" json:"is_location_pool"` - ReportID int32 `db:"report_id" json:"-"` - SourceContainer bool `db:"source_container" json:"source_container"` - SourceDescription string `db:"source_description" json:"source_description"` - SourceGutter bool `db:"source_gutter" json:"source_gutter"` - SourceStagnant bool `db:"source_stagnant" json:"source_stagnant"` - TODDay bool `db:"tod_day" json:"time_of_day_day"` - TODEarly bool `db:"tod_early" json:"time_of_day_early"` - TODEvening bool `db:"tod_evening" json:"time_of_day_evening"` - TODNight bool `db:"tod_night" json:"time_of_day_night"` -} - -func nuisancesByReportID(ctx context.Context, report_ids []int32) (map[int32]*Nuisance, error) { +func nuisancesByReportID(ctx context.Context, report_ids []int32) (map[int32]*types.Nuisance, error) { rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( sm.Columns( "additional_info", @@ -57,13 +39,13 @@ func nuisancesByReportID(ctx context.Context, report_ids []int32) (map[int32]*Nu sm.Where(psql.Quote("report_id").EQ( psql.Any(report_ids), )), - ), scan.StructMapper[Nuisance]()) + ), scan.StructMapper[types.Nuisance]()) if err != nil { return nil, fmt.Errorf("query nuisance: %w", err) } - results := make(map[int32]*Nuisance, len(rows)) + results := make(map[int32]*types.Nuisance, len(rows)) for _, row := range rows { - results[row.ReportID] = &Nuisance{ + results[row.ReportID] = &types.Nuisance{ AdditionalInfo: row.AdditionalInfo, Duration: row.Duration, IsLocationBackyard: row.IsLocationBackyard, diff --git a/platform/publicreport/report.go b/platform/publicreport/report.go index 8bad0531..dabe6cfe 100644 --- a/platform/publicreport/report.go +++ b/platform/publicreport/report.go @@ -3,10 +3,10 @@ package publicreport import ( "context" "fmt" - "time" "github.com/Gleipnir-Technology/bob" "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" "github.com/Gleipnir-Technology/bob/dialect/psql/sm" //"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" @@ -17,47 +17,15 @@ import ( "github.com/stephenafamo/scan" ) -type Report struct { - Log []LogEntry `db:"-" json:"log"` - Address types.Address `db:"address" json:"address"` - AddressRaw string `db:"address_raw" json:"address_raw"` - Created time.Time `db:"created" json:"created"` - ID int32 `db:"id" json:"-"` - Images []types.Image `db:"images" json:"images"` - Location *types.Location `db:"location" json:"location"` - Nuisance *Nuisance `db:"nuisance" json:"nuisance"` - PublicID string `db:"public_id" json:"public_id"` - Reporter types.Contact `db:"reporter" json:"reporter"` - Status string `db:"status" json:"status"` - Type string `db:"report_type" json:"type"` - Water *Water `db:"water" json:"water"` -} - -func ReportsForOrganization(ctx context.Context, org_id int32) ([]*Report, error) { - rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( - sm.Columns( - "address_country AS \"address.country\"", - "address_locality AS \"address.locality\"", - "address_number AS \"address.number\"", - "address_postal_code AS \"address.postal_code\"", - "address_raw AS address_raw", - "address_region AS \"address.region\"", - "address_street AS \"address.street\"", - "created", - "id", - "COALESCE(ST_Y(location::geometry::geometry(point, 4326)), 0.0) AS \"location.latitude\"", - "COALESCE(ST_X(location::geometry::geometry(point, 4326)), 0.0) AS \"location.longitude\"", - "public_id", - "report_type", - "reporter_email AS \"reporter.email\"", - "reporter_name AS \"reporter.name\"", - "reporter_phone AS \"reporter.phone\"", - "status", - ), - sm.From("publicreport.report"), - sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))), +func ReportsForOrganization(ctx context.Context, org_id int32) ([]*types.Report, error) { + query := reportQuery(org_id) + query.Apply( sm.Where(psql.Quote("publicreport", "report", "reviewed").IsNull()), - ), scan.StructMapper[Report]()) + ) + return reportQueryToRows(ctx, org_id, query) +} +func reportQueryToRows(ctx context.Context, org_id int32, query bob.BaseQuery[*dialect.SelectQuery]) ([]*types.Report, error) { + rows, err := bob.All(ctx, db.PGInstance.BobDB, query, scan.StructMapper[types.Report]()) if err != nil { return nil, fmt.Errorf("get reports: %w", err) @@ -83,7 +51,7 @@ func ReportsForOrganization(ctx context.Context, org_id int32) ([]*Report, error return nil, fmt.Errorf("waters: %w", err) } - results := make([]*Report, len(rows)) + results := make([]*types.Report, len(rows)) for i, row := range rows { images, ok := images_by_id[row.ID] if ok { @@ -101,6 +69,13 @@ func ReportsForOrganization(ctx context.Context, org_id int32) ([]*Report, error } return results, nil } +func Reports(ctx context.Context, org_id int32, ids []int32) ([]*types.Report, error) { + query := reportQuery(org_id) + query.Apply( + sm.Where(psql.Quote("publicreport", "report", "reviewed").IsNull()), + ) + return reportQueryToRows(ctx, org_id, query) +} func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) { type _Row struct { Count uint `db:"count"` @@ -117,3 +92,28 @@ func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error } return row.Count, nil } +func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] { + return psql.Select( + sm.Columns( + "address_country AS \"address.country\"", + "address_locality AS \"address.locality\"", + "address_number AS \"address.number\"", + "address_postal_code AS \"address.postal_code\"", + "address_raw AS address_raw", + "address_region AS \"address.region\"", + "address_street AS \"address.street\"", + "created", + "id", + "COALESCE(ST_Y(location::geometry::geometry(point, 4326)), 0.0) AS \"location.latitude\"", + "COALESCE(ST_X(location::geometry::geometry(point, 4326)), 0.0) AS \"location.longitude\"", + "public_id", + "report_type", + "reporter_email AS \"reporter.email\"", + "reporter_name AS \"reporter.name\"", + "reporter_phone AS \"reporter.phone\"", + "status", + ), + sm.From("publicreport.report"), + sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))), + ) +} diff --git a/platform/publicreport/water.go b/platform/publicreport/water.go index b9e3240a..0bcff03e 100644 --- a/platform/publicreport/water.go +++ b/platform/publicreport/water.go @@ -16,25 +16,7 @@ import ( "github.com/stephenafamo/scan" ) -type Water struct { - AccessComments string `db:"access_comments" json:"access_comments"` - AccessGate bool `db:"access_gate" json:"access_gate"` - AccessFence bool `db:"access_fence" json:"access_fence"` - AccessLocked bool `db:"access_locked" json:"access_locked"` - AccessDog bool `db:"access_dog" json:"access_dog"` - AccessOther bool `db:"access_other" json:"access_other"` - Comments string `db:"comments" json:"comments"` - HasAdult bool `db:"has_adult" json:"has_adult"` - HasBackyardPermission bool `db:"has_backyard_permission" json:"has_backyard_permission"` - HasLarvae bool `db:"has_larvae" json:"has_larvae"` - HasPupae bool `db:"has_pupae" json:"has_pupae"` - IsReporterConfidential bool `db:"is_reporter_confidential" json:"is_reporter_confidential"` - IsReporterOwner bool `db:"is_reporter_owner" json:"is_reporter_owner"` - Owner types.Contact `db:"owner" json:"owner"` - ReportID int32 `db:"report_id" json:"-"` -} - -func watersByReportID(ctx context.Context, report_ids []int32) (map[int32]*Water, error) { +func watersByReportID(ctx context.Context, report_ids []int32) (map[int32]*types.Water, error) { rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( sm.Columns( "access_comments", @@ -59,13 +41,13 @@ func watersByReportID(ctx context.Context, report_ids []int32) (map[int32]*Water sm.Where(psql.Quote("report_id").EQ( psql.Any(report_ids), )), - ), scan.StructMapper[Water]()) + ), scan.StructMapper[types.Water]()) if err != nil { return nil, fmt.Errorf("query water: %w", err) } - results := make(map[int32]*Water, len(rows)) + results := make(map[int32]*types.Water, len(rows)) for _, row := range rows { - results[row.ReportID] = &Water{ + results[row.ReportID] = &types.Water{ AccessComments: row.AccessComments, AccessGate: row.AccessGate, AccessFence: row.AccessFence, diff --git a/platform/review.go b/platform/review.go index 415973a7..47627a60 100644 --- a/platform/review.go +++ b/platform/review.go @@ -141,14 +141,15 @@ func commitReviewPool(ctx context.Context, txn bob.Tx, user User, review_task_po return nhttp.NewError("find feature %d: %w", feature_pool.FeatureID, err) } signal, err := models.Signals.Insert(&models.SignalSetter{ - Addressed: omitnull.FromPtr[time.Time](nil), - Addressor: omitnull.FromPtr[int32](nil), - Created: omit.From(time.Now()), - Creator: omit.From[int32](int32(user.ID)), + Addressed: omitnull.FromPtr[time.Time](nil), + Addressor: omitnull.FromPtr[int32](nil), + Created: omit.From(time.Now()), + Creator: omit.From[int32](int32(user.ID)), + FeaturePoolFeatureID: omitnull.From(feature_pool.FeatureID), //ID: omit.Val[int32], OrganizationID: omit.From(user.Organization.ID()), + ReportID: omitnull.FromPtr[int32](nil), Species: omitnull.FromPtr[enums.Mosquitospecies](nil), - Title: omit.From[string](""), Type: omit.From(enums.SignaltypeFlyoverPool), SiteID: omitnull.From(feature.SiteID), Location: omit.From[string](feature.Location.GetOr("")), diff --git a/platform/signal.go b/platform/signal.go index 996c44ca..2647855c 100644 --- a/platform/signal.go +++ b/platform/signal.go @@ -15,6 +15,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/platform/event" + "github.com/Gleipnir-Technology/nidus-sync/platform/publicreport" "github.com/Gleipnir-Technology/nidus-sync/platform/types" //"github.com/Gleipnir-Technology/nidus-sync/platform/geocode" //"github.com/Gleipnir-Technology/nidus-sync/platform/geom" @@ -32,8 +33,9 @@ type Signal struct { Creator int32 `db:"creator" json:"creator"` ID int32 `db:"id" json:"id"` Location types.Location `db:"location" json:"location"` + Pool *Pool `db:"pool" json:"pool"` + Report *types.Report `db:"report" json:"report"` Species *string `db:"species" json:"species"` - Title string `db:"title" json:"title"` Type string `db:"type" json:"type"` } @@ -115,16 +117,17 @@ func SignalCreateFromPublicreport(ctx context.Context, user User, report_id stri } log.Debug().Str("location", location).Msg("inserting signal") signal, err := models.Signals.Insert(&models.SignalSetter{ - Addressed: omitnull.FromPtr[time.Time](nil), - Addressor: omitnull.FromPtr[int32](nil), - Created: omit.From(time.Now()), - Creator: omit.From(int32(user.ID)), + Addressed: omitnull.FromPtr[time.Time](nil), + Addressor: omitnull.FromPtr[int32](nil), + Created: omit.From(time.Now()), + Creator: omit.From(int32(user.ID)), + FeaturePoolFeatureID: omitnull.FromPtr[int32](nil), // ID OrganizationID: omit.From(int32(user.Organization.ID())), Location: omit.From(location), + ReportID: omitnull.From(report.ID), Species: omitnull.FromPtr[enums.Mosquitospecies](nil), SiteID: omitnull.From(site_id), - Title: omit.From[string](""), Type: omit.From[enums.Signaltype](signal_type), }).One(ctx, txn) if err != nil { @@ -146,7 +149,8 @@ func SignalCreateFromPublicreport(ctx context.Context, user User, report_id stri return &signal.ID, nil } -func SignalList(ctx context.Context, user User, limit int) ([]Signal, error) { +func SignalList(ctx context.Context, user User, limit int) ([]*Signal, error) { + org_id := user.Organization.ID() rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( sm.Columns( "signal.addressed AS addressed", @@ -154,8 +158,9 @@ func SignalList(ctx context.Context, user User, limit int) ([]Signal, error) { "signal.created AS created", "signal.creator AS creator", "signal.id AS id", + "COALESCE(signal.feature_pool_feature_id, 0) AS \"pool.id\"", + "COALESCE(signal.report_id, 0) AS \"report.id\"", "signal.species AS species", - "signal.title AS title", "signal.type_ AS type", "address.country AS \"address.country\"", "address.locality AS \"address.locality\"", @@ -164,8 +169,9 @@ func SignalList(ctx context.Context, user User, limit int) ([]Signal, error) { "address.region AS \"address.region\"", "address.street AS \"address.street\"", "address.unit AS \"address.unit\"", - "ST_Y(address.location) AS \"location.latitude\"", - "ST_X(address.location) AS \"location.longitude\"", + // This will work great, up until we add polygons to signal + "ST_Y(signal.location) AS \"location.latitude\"", + "ST_X(signal.location) AS \"location.longitude\"", ), sm.From("signal"), sm.LeftJoin("site").OnEQ( @@ -176,13 +182,55 @@ func SignalList(ctx context.Context, user User, limit int) ([]Signal, error) { psql.Quote("site", "address_id"), psql.Quote("address", "id"), ), - sm.Where(psql.Quote("signal", "organization_id").EQ(psql.Arg(user.Organization.ID()))), + sm.Where(psql.Quote("signal", "organization_id").EQ(psql.Arg(org_id))), sm.Where(psql.Quote("signal", "addressed").IsNull()), sm.Limit(limit), - ), scan.StructMapper[Signal]()) + ), scan.StructMapper[*Signal]()) log.Debug().Int("len", len(rows)).Msg("got signals") if err != nil { return nil, fmt.Errorf("failed to get signals: %w", err) } + report_ids := make([]int32, 0) + pool_ids := make([]int32, 0) + for _, row := range rows { + if row.Report.ID != 0 { + report_ids = append(report_ids, row.Report.ID) + } else if row.Pool.ID != 0 { + pool_ids = append(pool_ids, row.Pool.ID) + } + } + pools, err := poolList(ctx, org_id, pool_ids) + if err != nil { + return nil, fmt.Errorf("getting pools by ID: %w", err) + } + reports, err := publicreport.Reports(ctx, org_id, report_ids) + if err != nil { + return nil, fmt.Errorf("getting reports by ID: %w", err) + } + pool_map := make(map[int32]*Pool, len(pools)) + for _, pool := range pools { + pool_map[pool.ID] = pool + log.Debug().Int32("pool", pool.ID).Msg("Added to map") + } + report_map := make(map[int32]*types.Report, len(report_ids)) + for _, report := range reports { + report_map[report.ID] = report + } + for _, row := range rows { + if row.Pool.ID != 0 { + p, ok := pool_map[row.Pool.ID] + if !ok { + return nil, fmt.Errorf("failed to get pool %d for %d", row.Pool.ID, row.ID) + } + if p == nil { + return nil, fmt.Errorf("got nil pool from %d for %d", row.Pool.ID, row.ID) + } + row.Pool = p + row.Report = nil + } else if row.Report.ID != 0 { + row.Pool = nil + row.Report = report_map[row.Report.ID] + } + } return rows, nil } diff --git a/platform/types/log_entry.go b/platform/types/log_entry.go new file mode 100644 index 00000000..823101d5 --- /dev/null +++ b/platform/types/log_entry.go @@ -0,0 +1,14 @@ +package types + +import ( + "time" +) + +type LogEntry struct { + Created time.Time `db:"created" json:"created"` + ID int32 `db:"id" json:"-"` + Message string `db:"message" json:"message"` + ReportID int32 `db:"report_id" json:"-"` + Type string `db:"type_" json:"type"` + UserID *int32 `db:"user_id" json:"user_id"` +} diff --git a/platform/types/nuisance.go b/platform/types/nuisance.go new file mode 100644 index 00000000..ed82debb --- /dev/null +++ b/platform/types/nuisance.go @@ -0,0 +1,20 @@ +package types + +type Nuisance struct { + AdditionalInfo string `db:"additional_info" json:"additional_info"` + Duration string `db:"duration" json:"duration"` + IsLocationBackyard bool `db:"is_location_backyard" json:"is_location_backyard"` + IsLocationFrontyard bool `db:"is_location_frontyard" json:"is_location_frontyard"` + IsLocationGarden bool `db:"is_location_garden" json:"is_location_garden"` + IsLocationOther bool `db:"is_location_other" json:"is_location_other"` + IsLocationPool bool `db:"is_location_pool" json:"is_location_pool"` + ReportID int32 `db:"report_id" json:"-"` + SourceContainer bool `db:"source_container" json:"source_container"` + SourceDescription string `db:"source_description" json:"source_description"` + SourceGutter bool `db:"source_gutter" json:"source_gutter"` + SourceStagnant bool `db:"source_stagnant" json:"source_stagnant"` + TODDay bool `db:"tod_day" json:"time_of_day_day"` + TODEarly bool `db:"tod_early" json:"time_of_day_early"` + TODEvening bool `db:"tod_evening" json:"time_of_day_evening"` + TODNight bool `db:"tod_night" json:"time_of_day_night"` +} diff --git a/platform/types/report.go b/platform/types/report.go new file mode 100644 index 00000000..1a56b5c6 --- /dev/null +++ b/platform/types/report.go @@ -0,0 +1,21 @@ +package types + +import ( + "time" +) + +type Report struct { + Log []LogEntry `db:"-" json:"log"` + Address Address `db:"address" json:"address"` + AddressRaw string `db:"address_raw" json:"address_raw"` + Created time.Time `db:"created" json:"created"` + ID int32 `db:"id" json:"-"` + Images []Image `db:"images" json:"images"` + Location *Location `db:"location" json:"location"` + Nuisance *Nuisance `db:"nuisance" json:"nuisance"` + PublicID string `db:"public_id" json:"public_id"` + Reporter Contact `db:"reporter" json:"reporter"` + Status string `db:"status" json:"status"` + Type string `db:"report_type" json:"type"` + Water *Water `db:"water" json:"water"` +} diff --git a/platform/types/water.go b/platform/types/water.go new file mode 100644 index 00000000..b4ae8e68 --- /dev/null +++ b/platform/types/water.go @@ -0,0 +1,19 @@ +package types + +type Water struct { + AccessComments string `db:"access_comments" json:"access_comments"` + AccessGate bool `db:"access_gate" json:"access_gate"` + AccessFence bool `db:"access_fence" json:"access_fence"` + AccessLocked bool `db:"access_locked" json:"access_locked"` + AccessDog bool `db:"access_dog" json:"access_dog"` + AccessOther bool `db:"access_other" json:"access_other"` + Comments string `db:"comments" json:"comments"` + HasAdult bool `db:"has_adult" json:"has_adult"` + HasBackyardPermission bool `db:"has_backyard_permission" json:"has_backyard_permission"` + HasLarvae bool `db:"has_larvae" json:"has_larvae"` + HasPupae bool `db:"has_pupae" json:"has_pupae"` + IsReporterConfidential bool `db:"is_reporter_confidential" json:"is_reporter_confidential"` + IsReporterOwner bool `db:"is_reporter_owner" json:"is_reporter_owner"` + Owner Contact `db:"owner" json:"owner"` + ReportID int32 `db:"report_id" json:"-"` +}