From a9b0a55f20302fc4c2c206001a71c03409b9bcb0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 31 Jan 2026 20:08:08 +0000 Subject: [PATCH] Create report platform layer Rework phone subscription at the database layer so that we have a seprate phone status and subscriptions to district communications. --- .../district_subscription_email.bob.go | 17 + .../district_subscription_phone.bob.go | 17 + db/dbinfo/comms.phone.bob.go | 16 +- db/dbinfo/district_subscription_email.bob.go | 132 +++ db/dbinfo/district_subscription_phone.bob.go | 132 +++ db/enums/enums.bob.go | 76 ++ db/factory/bobfactory_context.bob.go | 14 + db/factory/bobfactory_main.bob.go | 99 ++- db/factory/bobfactory_random.bob.go | 10 + db/factory/comms.email_contact.bob.go | 85 ++ db/factory/comms.phone.bob.go | 158 +++- db/factory/district_subscription_email.bob.go | 431 ++++++++++ db/factory/district_subscription_phone.bob.go | 431 ++++++++++ db/factory/organization.bob.go | 316 ++++++-- .../00048_comms_phone_subscribed.sql | 21 + db/models/bob_joins.bob.go | 4 + db/models/bob_loaders.bob.go | 8 + db/models/bob_where.bob.go | 6 + db/models/comms.email_contact.bob.go | 289 +++++++ db/models/comms.phone.bob.go | 349 +++++++- db/models/district_subscription_email.bob.go | 766 ++++++++++++++++++ db/models/district_subscription_phone.bob.go | 766 ++++++++++++++++++ db/models/organization.bob.go | 576 +++++++++++++ platform/report/notification.go | 128 +++ platform/text/report-subscription.go | 10 +- platform/text/text.go | 31 +- rmo/form.go | 26 + rmo/notification.go | 61 ++ rmo/nuisance.go | 3 +- rmo/pool.go | 3 +- rmo/quick.go | 56 +- rmo/report.go | 33 - rmo/root.go | 7 - rmo/status.go | 24 +- 34 files changed, 4844 insertions(+), 257 deletions(-) create mode 100644 db/dberrors/district_subscription_email.bob.go create mode 100644 db/dberrors/district_subscription_phone.bob.go create mode 100644 db/dbinfo/district_subscription_email.bob.go create mode 100644 db/dbinfo/district_subscription_phone.bob.go create mode 100644 db/factory/district_subscription_email.bob.go create mode 100644 db/factory/district_subscription_phone.bob.go create mode 100644 db/migrations/00048_comms_phone_subscribed.sql create mode 100644 db/models/district_subscription_email.bob.go create mode 100644 db/models/district_subscription_phone.bob.go create mode 100644 platform/report/notification.go create mode 100644 rmo/form.go create mode 100644 rmo/notification.go delete mode 100644 rmo/report.go diff --git a/db/dberrors/district_subscription_email.bob.go b/db/dberrors/district_subscription_email.bob.go new file mode 100644 index 00000000..86a8fba1 --- /dev/null +++ b/db/dberrors/district_subscription_email.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var DistrictSubscriptionEmailErrors = &districtSubscriptionEmailErrors{ + ErrUniqueDistrictSubscriptionEmailPkey: &UniqueConstraintError{ + schema: "", + table: "district_subscription_email", + columns: []string{"organization_id", "email_contact_address"}, + s: "district_subscription_email_pkey", + }, +} + +type districtSubscriptionEmailErrors struct { + ErrUniqueDistrictSubscriptionEmailPkey *UniqueConstraintError +} diff --git a/db/dberrors/district_subscription_phone.bob.go b/db/dberrors/district_subscription_phone.bob.go new file mode 100644 index 00000000..06e7d427 --- /dev/null +++ b/db/dberrors/district_subscription_phone.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var DistrictSubscriptionPhoneErrors = &districtSubscriptionPhoneErrors{ + ErrUniqueDistrictSubscriptionPhonePkey: &UniqueConstraintError{ + schema: "", + table: "district_subscription_phone", + columns: []string{"organization_id", "phone_e164"}, + s: "district_subscription_phone_pkey", + }, +} + +type districtSubscriptionPhoneErrors struct { + ErrUniqueDistrictSubscriptionPhonePkey *UniqueConstraintError +} diff --git a/db/dbinfo/comms.phone.bob.go b/db/dbinfo/comms.phone.bob.go index d769ef1d..402cd6aa 100644 --- a/db/dbinfo/comms.phone.bob.go +++ b/db/dbinfo/comms.phone.bob.go @@ -27,9 +27,18 @@ var CommsPhones = Table[ IsSubscribed: column{ Name: "is_subscribed", DBType: "boolean", - Default: "NULL", + Default: "", Comment: "", - Nullable: true, + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Status: column{ + Name: "status", + DBType: "comms.phonestatustype", + Default: "", + Comment: "", + Nullable: false, Generated: false, AutoIncr: false, }, @@ -65,11 +74,12 @@ var CommsPhones = Table[ type commsPhoneColumns struct { E164 column IsSubscribed column + Status column } func (c commsPhoneColumns) AsSlice() []column { return []column{ - c.E164, c.IsSubscribed, + c.E164, c.IsSubscribed, c.Status, } } diff --git a/db/dbinfo/district_subscription_email.bob.go b/db/dbinfo/district_subscription_email.bob.go new file mode 100644 index 00000000..92e1b428 --- /dev/null +++ b/db/dbinfo/district_subscription_email.bob.go @@ -0,0 +1,132 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +var DistrictSubscriptionEmails = Table[ + districtSubscriptionEmailColumns, + districtSubscriptionEmailIndexes, + districtSubscriptionEmailForeignKeys, + districtSubscriptionEmailUniques, + districtSubscriptionEmailChecks, +]{ + Schema: "", + Name: "district_subscription_email", + Columns: districtSubscriptionEmailColumns{ + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + EmailContactAddress: column{ + Name: "email_contact_address", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: districtSubscriptionEmailIndexes{ + DistrictSubscriptionEmailPkey: index{ + Type: "btree", + Name: "district_subscription_email_pkey", + Columns: []indexColumn{ + { + Name: "organization_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "email_contact_address", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "district_subscription_email_pkey", + Columns: []string{"organization_id", "email_contact_address"}, + Comment: "", + }, + ForeignKeys: districtSubscriptionEmailForeignKeys{ + DistrictSubscriptionEmailDistrictSubscriptionEmailEmailContactAddressFkey: foreignKey{ + constraint: constraint{ + Name: "district_subscription_email.district_subscription_email_email_contact_address_fkey", + Columns: []string{"email_contact_address"}, + Comment: "", + }, + ForeignTable: "comms.email_contact", + ForeignColumns: []string{"address"}, + }, + DistrictSubscriptionEmailDistrictSubscriptionEmailOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "district_subscription_email.district_subscription_email_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type districtSubscriptionEmailColumns struct { + OrganizationID column + EmailContactAddress column +} + +func (c districtSubscriptionEmailColumns) AsSlice() []column { + return []column{ + c.OrganizationID, c.EmailContactAddress, + } +} + +type districtSubscriptionEmailIndexes struct { + DistrictSubscriptionEmailPkey index +} + +func (i districtSubscriptionEmailIndexes) AsSlice() []index { + return []index{ + i.DistrictSubscriptionEmailPkey, + } +} + +type districtSubscriptionEmailForeignKeys struct { + DistrictSubscriptionEmailDistrictSubscriptionEmailEmailContactAddressFkey foreignKey + DistrictSubscriptionEmailDistrictSubscriptionEmailOrganizationIDFkey foreignKey +} + +func (f districtSubscriptionEmailForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.DistrictSubscriptionEmailDistrictSubscriptionEmailEmailContactAddressFkey, f.DistrictSubscriptionEmailDistrictSubscriptionEmailOrganizationIDFkey, + } +} + +type districtSubscriptionEmailUniques struct{} + +func (u districtSubscriptionEmailUniques) AsSlice() []constraint { + return []constraint{} +} + +type districtSubscriptionEmailChecks struct{} + +func (c districtSubscriptionEmailChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/district_subscription_phone.bob.go b/db/dbinfo/district_subscription_phone.bob.go new file mode 100644 index 00000000..c7c43462 --- /dev/null +++ b/db/dbinfo/district_subscription_phone.bob.go @@ -0,0 +1,132 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +var DistrictSubscriptionPhones = Table[ + districtSubscriptionPhoneColumns, + districtSubscriptionPhoneIndexes, + districtSubscriptionPhoneForeignKeys, + districtSubscriptionPhoneUniques, + districtSubscriptionPhoneChecks, +]{ + Schema: "", + Name: "district_subscription_phone", + Columns: districtSubscriptionPhoneColumns{ + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + PhoneE164: column{ + Name: "phone_e164", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: districtSubscriptionPhoneIndexes{ + DistrictSubscriptionPhonePkey: index{ + Type: "btree", + Name: "district_subscription_phone_pkey", + Columns: []indexColumn{ + { + Name: "organization_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "phone_e164", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "district_subscription_phone_pkey", + Columns: []string{"organization_id", "phone_e164"}, + Comment: "", + }, + ForeignKeys: districtSubscriptionPhoneForeignKeys{ + DistrictSubscriptionPhoneDistrictSubscriptionPhoneOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "district_subscription_phone.district_subscription_phone_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, + DistrictSubscriptionPhoneDistrictSubscriptionPhonePhoneE164Fkey: foreignKey{ + constraint: constraint{ + Name: "district_subscription_phone.district_subscription_phone_phone_e164_fkey", + Columns: []string{"phone_e164"}, + Comment: "", + }, + ForeignTable: "comms.phone", + ForeignColumns: []string{"e164"}, + }, + }, + + Comment: "", +} + +type districtSubscriptionPhoneColumns struct { + OrganizationID column + PhoneE164 column +} + +func (c districtSubscriptionPhoneColumns) AsSlice() []column { + return []column{ + c.OrganizationID, c.PhoneE164, + } +} + +type districtSubscriptionPhoneIndexes struct { + DistrictSubscriptionPhonePkey index +} + +func (i districtSubscriptionPhoneIndexes) AsSlice() []index { + return []index{ + i.DistrictSubscriptionPhonePkey, + } +} + +type districtSubscriptionPhoneForeignKeys struct { + DistrictSubscriptionPhoneDistrictSubscriptionPhoneOrganizationIDFkey foreignKey + DistrictSubscriptionPhoneDistrictSubscriptionPhonePhoneE164Fkey foreignKey +} + +func (f districtSubscriptionPhoneForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.DistrictSubscriptionPhoneDistrictSubscriptionPhoneOrganizationIDFkey, f.DistrictSubscriptionPhoneDistrictSubscriptionPhonePhoneE164Fkey, + } +} + +type districtSubscriptionPhoneUniques struct{} + +func (u districtSubscriptionPhoneUniques) AsSlice() []constraint { + return []constraint{} +} + +type districtSubscriptionPhoneChecks struct{} + +func (c districtSubscriptionPhoneChecks) AsSlice() []check { + return []check{} +} diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 6e603c4f..14d12cac 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -272,6 +272,82 @@ func (e *CommsMessagetypeemail) Scan(value any) error { return nil } +// Enum values for CommsPhonestatustype +const ( + CommsPhonestatustypeUnconfirmed CommsPhonestatustype = "unconfirmed" + CommsPhonestatustypeOkToSend CommsPhonestatustype = "ok-to-send" + CommsPhonestatustypeStopped CommsPhonestatustype = "stopped" +) + +func AllCommsPhonestatustype() []CommsPhonestatustype { + return []CommsPhonestatustype{ + CommsPhonestatustypeUnconfirmed, + CommsPhonestatustypeOkToSend, + CommsPhonestatustypeStopped, + } +} + +type CommsPhonestatustype string + +func (e CommsPhonestatustype) String() string { + return string(e) +} + +func (e CommsPhonestatustype) Valid() bool { + switch e { + case CommsPhonestatustypeUnconfirmed, + CommsPhonestatustypeOkToSend, + CommsPhonestatustypeStopped: + return true + default: + return false + } +} + +// useful when testing in other packages +func (e CommsPhonestatustype) All() []CommsPhonestatustype { + return AllCommsPhonestatustype() +} + +func (e CommsPhonestatustype) MarshalText() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsPhonestatustype) UnmarshalText(text []byte) error { + return e.Scan(text) +} + +func (e CommsPhonestatustype) MarshalBinary() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsPhonestatustype) UnmarshalBinary(data []byte) error { + return e.Scan(data) +} + +func (e CommsPhonestatustype) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *CommsPhonestatustype) Scan(value any) error { + switch x := value.(type) { + case string: + *e = CommsPhonestatustype(x) + case []byte: + *e = CommsPhonestatustype(x) + case nil: + return fmt.Errorf("cannot nil into CommsPhonestatustype") + default: + return fmt.Errorf("cannot scan type %T: %v", value, value) + } + + if !e.Valid() { + return fmt.Errorf("invalid CommsPhonestatustype value: %s", *e) + } + + return nil +} + // Enum values for CommsTextjobsource const ( CommsTextjobsourceRmo CommsTextjobsource = "rmo" diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 0643830a..1ba4c471 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -20,6 +20,7 @@ var ( // Relationship Contexts for comms.email_contact commsEmailContactWithParentsCascadingCtx = newContextual[bool]("commsEmailContactWithParentsCascading") commsEmailContactRelDestinationEmailLogsCtx = newContextual[bool]("comms.email_contact.comms.email_log.comms.email_log.email_log_destination_fkey") + commsEmailContactRelOrganizationsCtx = newContextual[bool]("comms.email_contact.organization.district_subscription_email.district_subscription_email_email_contact_address_fkeydistrict_subscription_email.district_subscription_email_organization_id_fkey") // Relationship Contexts for comms.email_log commsEmailLogWithParentsCascadingCtx = newContextual[bool]("commsEmailLogWithParentsCascading") @@ -35,6 +36,7 @@ var ( commsPhoneRelDestinationTextJobsCtx = newContextual[bool]("comms.phone.comms.text_job.comms.text_job.text_job_destination_fkey") commsPhoneRelDestinationTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") commsPhoneRelSourceTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_source_fkey") + commsPhoneRelOrganizationsCtx = newContextual[bool]("comms.phone.organization.district_subscription_phone.district_subscription_phone_organization_id_fkeydistrict_subscription_phone.district_subscription_phone_phone_e164_fkey") // Relationship Contexts for comms.text_job commsTextJobWithParentsCascadingCtx = newContextual[bool]("commsTextJobWithParentsCascading") @@ -45,6 +47,16 @@ var ( commsTextLogRelDestinationPhoneCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") commsTextLogRelSourcePhoneCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_source_fkey") + // Relationship Contexts for district_subscription_email + districtSubscriptionEmailWithParentsCascadingCtx = newContextual[bool]("districtSubscriptionEmailWithParentsCascading") + districtSubscriptionEmailRelEmailContactAddressEmailContactCtx = newContextual[bool]("comms.email_contact.district_subscription_email.district_subscription_email.district_subscription_email_email_contact_address_fkey") + districtSubscriptionEmailRelOrganizationCtx = newContextual[bool]("district_subscription_email.organization.district_subscription_email.district_subscription_email_organization_id_fkey") + + // Relationship Contexts for district_subscription_phone + districtSubscriptionPhoneWithParentsCascadingCtx = newContextual[bool]("districtSubscriptionPhoneWithParentsCascading") + districtSubscriptionPhoneRelOrganizationCtx = newContextual[bool]("district_subscription_phone.organization.district_subscription_phone.district_subscription_phone_organization_id_fkey") + districtSubscriptionPhoneRelPhoneE164PhoneCtx = newContextual[bool]("comms.phone.district_subscription_phone.district_subscription_phone.district_subscription_phone_phone_e164_fkey") + // Relationship Contexts for fieldseeker.containerrelate fieldseekerContainerrelateWithParentsCascadingCtx = newContextual[bool]("fieldseekerContainerrelateWithParentsCascading") fieldseekerContainerrelateRelOrganizationCtx = newContextual[bool]("fieldseeker.containerrelate.organization.fieldseeker.containerrelate.containerrelate_organization_id_fkey") @@ -216,6 +228,8 @@ var ( // Relationship Contexts for organization organizationWithParentsCascadingCtx = newContextual[bool]("organizationWithParentsCascading") + organizationRelEmailContactsCtx = newContextual[bool]("comms.email_contact.organization.district_subscription_email.district_subscription_email_email_contact_address_fkeydistrict_subscription_email.district_subscription_email_organization_id_fkey") + organizationRelPhonesCtx = newContextual[bool]("comms.phone.organization.district_subscription_phone.district_subscription_phone_organization_id_fkeydistrict_subscription_phone.district_subscription_phone_phone_e164_fkey") organizationRelContainerrelatesCtx = newContextual[bool]("fieldseeker.containerrelate.organization.fieldseeker.containerrelate.containerrelate_organization_id_fkey") organizationRelFieldscoutinglogsCtx = newContextual[bool]("fieldseeker.fieldscoutinglog.organization.fieldseeker.fieldscoutinglog.fieldscoutinglog_organization_id_fkey") organizationRelHabitatrelatesCtx = newContextual[bool]("fieldseeker.habitatrelate.organization.fieldseeker.habitatrelate.habitatrelate_organization_id_fkey") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index 3accd917..f7d7773f 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -27,6 +27,8 @@ type Factory struct { baseCommsPhoneMods CommsPhoneModSlice baseCommsTextJobMods CommsTextJobModSlice baseCommsTextLogMods CommsTextLogModSlice + baseDistrictSubscriptionEmailMods DistrictSubscriptionEmailModSlice + baseDistrictSubscriptionPhoneMods DistrictSubscriptionPhoneModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice baseFieldseekerFieldscoutinglogMods FieldseekerFieldscoutinglogModSlice baseFieldseekerHabitatrelateMods FieldseekerHabitatrelateModSlice @@ -191,6 +193,9 @@ func (f *Factory) FromExistingCommsEmailContact(m *models.CommsEmailContact) *Co if len(m.R.DestinationEmailLogs) > 0 { CommsEmailContactMods.AddExistingDestinationEmailLogs(m.R.DestinationEmailLogs...).Apply(ctx, o) } + if len(m.R.Organizations) > 0 { + CommsEmailContactMods.AddExistingOrganizations(m.R.Organizations...).Apply(ctx, o) + } return o } @@ -293,7 +298,8 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla o := &CommsPhoneTemplate{f: f, alreadyPersisted: true} o.E164 = func() string { return m.E164 } - o.IsSubscribed = func() null.Val[bool] { return m.IsSubscribed } + o.IsSubscribed = func() bool { return m.IsSubscribed } + o.Status = func() enums.CommsPhonestatustype { return m.Status } ctx := context.Background() if len(m.R.DestinationTextJobs) > 0 { @@ -305,6 +311,9 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla if len(m.R.SourceTextLogs) > 0 { CommsPhoneMods.AddExistingSourceTextLogs(m.R.SourceTextLogs...).Apply(ctx, o) } + if len(m.R.Organizations) > 0 { + CommsPhoneMods.AddExistingOrganizations(m.R.Organizations...).Apply(ctx, o) + } return o } @@ -385,6 +394,72 @@ func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLog return o } +func (f *Factory) NewDistrictSubscriptionEmail(mods ...DistrictSubscriptionEmailMod) *DistrictSubscriptionEmailTemplate { + return f.NewDistrictSubscriptionEmailWithContext(context.Background(), mods...) +} + +func (f *Factory) NewDistrictSubscriptionEmailWithContext(ctx context.Context, mods ...DistrictSubscriptionEmailMod) *DistrictSubscriptionEmailTemplate { + o := &DistrictSubscriptionEmailTemplate{f: f} + + if f != nil { + f.baseDistrictSubscriptionEmailMods.Apply(ctx, o) + } + + DistrictSubscriptionEmailModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingDistrictSubscriptionEmail(m *models.DistrictSubscriptionEmail) *DistrictSubscriptionEmailTemplate { + o := &DistrictSubscriptionEmailTemplate{f: f, alreadyPersisted: true} + + o.OrganizationID = func() int32 { return m.OrganizationID } + o.EmailContactAddress = func() string { return m.EmailContactAddress } + + ctx := context.Background() + if m.R.EmailContactAddressEmailContact != nil { + DistrictSubscriptionEmailMods.WithExistingEmailContactAddressEmailContact(m.R.EmailContactAddressEmailContact).Apply(ctx, o) + } + if m.R.Organization != nil { + DistrictSubscriptionEmailMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewDistrictSubscriptionPhone(mods ...DistrictSubscriptionPhoneMod) *DistrictSubscriptionPhoneTemplate { + return f.NewDistrictSubscriptionPhoneWithContext(context.Background(), mods...) +} + +func (f *Factory) NewDistrictSubscriptionPhoneWithContext(ctx context.Context, mods ...DistrictSubscriptionPhoneMod) *DistrictSubscriptionPhoneTemplate { + o := &DistrictSubscriptionPhoneTemplate{f: f} + + if f != nil { + f.baseDistrictSubscriptionPhoneMods.Apply(ctx, o) + } + + DistrictSubscriptionPhoneModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingDistrictSubscriptionPhone(m *models.DistrictSubscriptionPhone) *DistrictSubscriptionPhoneTemplate { + o := &DistrictSubscriptionPhoneTemplate{f: f, alreadyPersisted: true} + + o.OrganizationID = func() int32 { return m.OrganizationID } + o.PhoneE164 = func() string { return m.PhoneE164 } + + ctx := context.Background() + if m.R.Organization != nil { + DistrictSubscriptionPhoneMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } + if m.R.PhoneE164Phone != nil { + DistrictSubscriptionPhoneMods.WithExistingPhoneE164Phone(m.R.PhoneE164Phone).Apply(ctx, o) + } + + return o +} + func (f *Factory) NewFieldseekerContainerrelate(mods ...FieldseekerContainerrelateMod) *FieldseekerContainerrelateTemplate { return f.NewFieldseekerContainerrelateWithContext(context.Background(), mods...) } @@ -2641,6 +2716,12 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization o.Slug = func() null.Val[string] { return m.Slug } ctx := context.Background() + if len(m.R.EmailContacts) > 0 { + OrganizationMods.AddExistingEmailContacts(m.R.EmailContacts...).Apply(ctx, o) + } + if len(m.R.Phones) > 0 { + OrganizationMods.AddExistingPhones(m.R.Phones...).Apply(ctx, o) + } if len(m.R.Containerrelates) > 0 { OrganizationMods.AddExistingContainerrelates(m.R.Containerrelates...).Apply(ctx, o) } @@ -3323,6 +3404,22 @@ func (f *Factory) AddBaseCommsTextLogMod(mods ...CommsTextLogMod) { f.baseCommsTextLogMods = append(f.baseCommsTextLogMods, mods...) } +func (f *Factory) ClearBaseDistrictSubscriptionEmailMods() { + f.baseDistrictSubscriptionEmailMods = nil +} + +func (f *Factory) AddBaseDistrictSubscriptionEmailMod(mods ...DistrictSubscriptionEmailMod) { + f.baseDistrictSubscriptionEmailMods = append(f.baseDistrictSubscriptionEmailMods, mods...) +} + +func (f *Factory) ClearBaseDistrictSubscriptionPhoneMods() { + f.baseDistrictSubscriptionPhoneMods = nil +} + +func (f *Factory) AddBaseDistrictSubscriptionPhoneMod(mods ...DistrictSubscriptionPhoneMod) { + f.baseDistrictSubscriptionPhoneMods = append(f.baseDistrictSubscriptionPhoneMods, mods...) +} + func (f *Factory) ClearBaseFieldseekerContainerrelateMods() { f.baseFieldseekerContainerrelateMods = nil } diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index 70df0b0d..26a8f9c4 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -101,6 +101,16 @@ func random_enums_CommsMessagetypeemail(f *faker.Faker, limits ...string) enums. return all[f.IntBetween(0, len(all)-1)] } +func random_enums_CommsPhonestatustype(f *faker.Faker, limits ...string) enums.CommsPhonestatustype { + if f == nil { + f = &defaultFaker + } + + var e enums.CommsPhonestatustype + all := e.All() + return all[f.IntBetween(0, len(all)-1)] +} + func random_enums_CommsTextjobsource(f *faker.Faker, limits ...string) enums.CommsTextjobsource { if f == nil { f = &defaultFaker diff --git a/db/factory/comms.email_contact.bob.go b/db/factory/comms.email_contact.bob.go index a0ca1038..39750be6 100644 --- a/db/factory/comms.email_contact.bob.go +++ b/db/factory/comms.email_contact.bob.go @@ -47,12 +47,17 @@ type CommsEmailContactTemplate struct { type commsEmailContactR struct { DestinationEmailLogs []*commsEmailContactRDestinationEmailLogsR + Organizations []*commsEmailContactROrganizationsR } type commsEmailContactRDestinationEmailLogsR struct { number int o *CommsEmailLogTemplate } +type commsEmailContactROrganizationsR struct { + number int + o *OrganizationTemplate +} // Apply mods to the CommsEmailContactTemplate func (o *CommsEmailContactTemplate) Apply(ctx context.Context, mods ...CommsEmailContactMod) { @@ -76,6 +81,18 @@ func (t CommsEmailContactTemplate) setModelRels(o *models.CommsEmailContact) { } o.R.DestinationEmailLogs = rel } + + if t.r.Organizations != nil { + rel := models.OrganizationSlice{} + for _, r := range t.r.Organizations { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.R.EmailContacts = append(rel.R.EmailContacts, o) + } + rel = append(rel, related...) + } + o.R.Organizations = rel + } } // BuildSetter returns an *models.CommsEmailContactSetter @@ -197,6 +214,26 @@ func (o *CommsEmailContactTemplate) insertOptRels(ctx context.Context, exec bob. } } + isOrganizationsDone, _ := commsEmailContactRelOrganizationsCtx.Value(ctx) + if !isOrganizationsDone && o.r.Organizations != nil { + ctx = commsEmailContactRelOrganizationsCtx.WithValue(ctx, true) + for _, r := range o.r.Organizations { + if r.o.alreadyPersisted { + m.R.Organizations = append(m.R.Organizations, r.o.Build()) + } else { + rel1, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachOrganizations(ctx, exec, rel1...) + if err != nil { + return err + } + } + } + } + return err } @@ -476,3 +513,51 @@ func (m commsEmailContactMods) WithoutDestinationEmailLogs() CommsEmailContactMo o.r.DestinationEmailLogs = nil }) } + +func (m commsEmailContactMods) WithOrganizations(number int, related *OrganizationTemplate) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + o.r.Organizations = []*commsEmailContactROrganizationsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsEmailContactMods) WithNewOrganizations(number int, mods ...OrganizationMod) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + m.WithOrganizations(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailContactMods) AddOrganizations(number int, related *OrganizationTemplate) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + o.r.Organizations = append(o.r.Organizations, &commsEmailContactROrganizationsR{ + number: number, + o: related, + }) + }) +} + +func (m commsEmailContactMods) AddNewOrganizations(number int, mods ...OrganizationMod) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + m.AddOrganizations(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailContactMods) AddExistingOrganizations(existingModels ...*models.Organization) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + for _, em := range existingModels { + o.r.Organizations = append(o.r.Organizations, &commsEmailContactROrganizationsR{ + o: o.f.FromExistingOrganization(em), + }) + } + }) +} + +func (m commsEmailContactMods) WithoutOrganizations() CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + o.r.Organizations = nil + }) +} diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go index 5c5d9aec..c5adbbbd 100644 --- a/db/factory/comms.phone.bob.go +++ b/db/factory/comms.phone.bob.go @@ -8,10 +8,9 @@ import ( "testing" "github.com/Gleipnir-Technology/bob" + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" - "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" ) @@ -37,7 +36,8 @@ func (mods CommsPhoneModSlice) Apply(ctx context.Context, n *CommsPhoneTemplate) // all columns are optional and should be set by mods type CommsPhoneTemplate struct { E164 func() string - IsSubscribed func() null.Val[bool] + IsSubscribed func() bool + Status func() enums.CommsPhonestatustype r commsPhoneR f *Factory @@ -49,6 +49,7 @@ type commsPhoneR struct { DestinationTextJobs []*commsPhoneRDestinationTextJobsR DestinationTextLogs []*commsPhoneRDestinationTextLogsR SourceTextLogs []*commsPhoneRSourceTextLogsR + Organizations []*commsPhoneROrganizationsR } type commsPhoneRDestinationTextJobsR struct { @@ -63,6 +64,10 @@ type commsPhoneRSourceTextLogsR struct { number int o *CommsTextLogTemplate } +type commsPhoneROrganizationsR struct { + number int + o *OrganizationTemplate +} // Apply mods to the CommsPhoneTemplate func (o *CommsPhoneTemplate) Apply(ctx context.Context, mods ...CommsPhoneMod) { @@ -112,6 +117,18 @@ func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { } o.R.SourceTextLogs = rel } + + if t.r.Organizations != nil { + rel := models.OrganizationSlice{} + for _, r := range t.r.Organizations { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.R.Phones = append(rel.R.Phones, o) + } + rel = append(rel, related...) + } + o.R.Organizations = rel + } } // BuildSetter returns an *models.CommsPhoneSetter @@ -125,7 +142,11 @@ func (o CommsPhoneTemplate) BuildSetter() *models.CommsPhoneSetter { } if o.IsSubscribed != nil { val := o.IsSubscribed() - m.IsSubscribed = omitnull.FromNull(val) + m.IsSubscribed = omit.From(val) + } + if o.Status != nil { + val := o.Status() + m.Status = omit.From(val) } return m @@ -155,6 +176,9 @@ func (o CommsPhoneTemplate) Build() *models.CommsPhone { if o.IsSubscribed != nil { m.IsSubscribed = o.IsSubscribed() } + if o.Status != nil { + m.Status = o.Status() + } o.setModelRels(m) @@ -179,6 +203,14 @@ func ensureCreatableCommsPhone(m *models.CommsPhoneSetter) { val := random_string(nil) m.E164 = omit.From(val) } + if !(m.IsSubscribed.IsValue()) { + val := random_bool(nil) + m.IsSubscribed = omit.From(val) + } + if !(m.Status.IsValue()) { + val := random_enums_CommsPhonestatustype(nil) + m.Status = omit.From(val) + } } // insertOptRels creates and inserts any optional the relationships on *models.CommsPhone @@ -247,6 +279,26 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo } } + isOrganizationsDone, _ := commsPhoneRelOrganizationsCtx.Value(ctx) + if !isOrganizationsDone && o.r.Organizations != nil { + ctx = commsPhoneRelOrganizationsCtx.WithValue(ctx, true) + for _, r := range o.r.Organizations { + if r.o.alreadyPersisted { + m.R.Organizations = append(m.R.Organizations, r.o.Build()) + } else { + rel3, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachOrganizations(ctx, exec, rel3...) + if err != nil { + return err + } + } + } + } + return err } @@ -341,6 +393,7 @@ func (m commsPhoneMods) RandomizeAllColumns(f *faker.Faker) CommsPhoneMod { return CommsPhoneModSlice{ CommsPhoneMods.RandomE164(f), CommsPhoneMods.RandomIsSubscribed(f), + CommsPhoneMods.RandomStatus(f), } } @@ -376,14 +429,14 @@ func (m commsPhoneMods) RandomE164(f *faker.Faker) CommsPhoneMod { } // Set the model columns to this value -func (m commsPhoneMods) IsSubscribed(val null.Val[bool]) CommsPhoneMod { +func (m commsPhoneMods) IsSubscribed(val bool) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { - o.IsSubscribed = func() null.Val[bool] { return val } + o.IsSubscribed = func() bool { return val } }) } // Set the Column from the function -func (m commsPhoneMods) IsSubscribedFunc(f func() null.Val[bool]) CommsPhoneMod { +func (m commsPhoneMods) IsSubscribedFunc(f func() bool) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { o.IsSubscribed = f }) @@ -398,32 +451,41 @@ func (m commsPhoneMods) UnsetIsSubscribed() CommsPhoneMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used -// The generated value is sometimes null func (m commsPhoneMods) RandomIsSubscribed(f *faker.Faker) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { - o.IsSubscribed = func() null.Val[bool] { - if f == nil { - f = &defaultFaker - } - - val := random_bool(f) - return null.From(val) + o.IsSubscribed = func() bool { + return random_bool(f) } }) } +// Set the model columns to this value +func (m commsPhoneMods) Status(val enums.CommsPhonestatustype) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.Status = func() enums.CommsPhonestatustype { return val } + }) +} + +// Set the Column from the function +func (m commsPhoneMods) StatusFunc(f func() enums.CommsPhonestatustype) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.Status = f + }) +} + +// Clear any values for the column +func (m commsPhoneMods) UnsetStatus() CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.Status = nil + }) +} + // Generates a random value for the column using the given faker // if faker is nil, a default faker is used -// The generated value is never null -func (m commsPhoneMods) RandomIsSubscribedNotNull(f *faker.Faker) CommsPhoneMod { +func (m commsPhoneMods) RandomStatus(f *faker.Faker) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { - o.IsSubscribed = func() null.Val[bool] { - if f == nil { - f = &defaultFaker - } - - val := random_bool(f) - return null.From(val) + o.Status = func() enums.CommsPhonestatustype { + return random_enums_CommsPhonestatustype(f) } }) } @@ -580,3 +642,51 @@ func (m commsPhoneMods) WithoutSourceTextLogs() CommsPhoneMod { o.r.SourceTextLogs = nil }) } + +func (m commsPhoneMods) WithOrganizations(number int, related *OrganizationTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.Organizations = []*commsPhoneROrganizationsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsPhoneMods) WithNewOrganizations(number int, mods ...OrganizationMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + m.WithOrganizations(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddOrganizations(number int, related *OrganizationTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.Organizations = append(o.r.Organizations, &commsPhoneROrganizationsR{ + number: number, + o: related, + }) + }) +} + +func (m commsPhoneMods) AddNewOrganizations(number int, mods ...OrganizationMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + m.AddOrganizations(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddExistingOrganizations(existingModels ...*models.Organization) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + for _, em := range existingModels { + o.r.Organizations = append(o.r.Organizations, &commsPhoneROrganizationsR{ + o: o.f.FromExistingOrganization(em), + }) + } + }) +} + +func (m commsPhoneMods) WithoutOrganizations() CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.Organizations = nil + }) +} diff --git a/db/factory/district_subscription_email.bob.go b/db/factory/district_subscription_email.bob.go new file mode 100644 index 00000000..bfa7e834 --- /dev/null +++ b/db/factory/district_subscription_email.bob.go @@ -0,0 +1,431 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + + "github.com/Gleipnir-Technology/bob" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" +) + +type DistrictSubscriptionEmailMod interface { + Apply(context.Context, *DistrictSubscriptionEmailTemplate) +} + +type DistrictSubscriptionEmailModFunc func(context.Context, *DistrictSubscriptionEmailTemplate) + +func (f DistrictSubscriptionEmailModFunc) Apply(ctx context.Context, n *DistrictSubscriptionEmailTemplate) { + f(ctx, n) +} + +type DistrictSubscriptionEmailModSlice []DistrictSubscriptionEmailMod + +func (mods DistrictSubscriptionEmailModSlice) Apply(ctx context.Context, n *DistrictSubscriptionEmailTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// DistrictSubscriptionEmailTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type DistrictSubscriptionEmailTemplate struct { + OrganizationID func() int32 + EmailContactAddress func() string + + r districtSubscriptionEmailR + f *Factory + + alreadyPersisted bool +} + +type districtSubscriptionEmailR struct { + EmailContactAddressEmailContact *districtSubscriptionEmailREmailContactAddressEmailContactR + Organization *districtSubscriptionEmailROrganizationR +} + +type districtSubscriptionEmailREmailContactAddressEmailContactR struct { + o *CommsEmailContactTemplate +} +type districtSubscriptionEmailROrganizationR struct { + o *OrganizationTemplate +} + +// Apply mods to the DistrictSubscriptionEmailTemplate +func (o *DistrictSubscriptionEmailTemplate) Apply(ctx context.Context, mods ...DistrictSubscriptionEmailMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.DistrictSubscriptionEmail +// according to the relationships in the template. Nothing is inserted into the db +func (t DistrictSubscriptionEmailTemplate) setModelRels(o *models.DistrictSubscriptionEmail) { + if t.r.EmailContactAddressEmailContact != nil { + rel := t.r.EmailContactAddressEmailContact.o.Build() + o.EmailContactAddress = rel.Address // h2 + o.R.EmailContactAddressEmailContact = rel + } + + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + o.OrganizationID = rel.ID // h2 + o.R.Organization = rel + } +} + +// BuildSetter returns an *models.DistrictSubscriptionEmailSetter +// this does nothing with the relationship templates +func (o DistrictSubscriptionEmailTemplate) BuildSetter() *models.DistrictSubscriptionEmailSetter { + m := &models.DistrictSubscriptionEmailSetter{} + + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omit.From(val) + } + if o.EmailContactAddress != nil { + val := o.EmailContactAddress() + m.EmailContactAddress = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.DistrictSubscriptionEmailSetter +// this does nothing with the relationship templates +func (o DistrictSubscriptionEmailTemplate) BuildManySetter(number int) []*models.DistrictSubscriptionEmailSetter { + m := make([]*models.DistrictSubscriptionEmailSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.DistrictSubscriptionEmail +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use DistrictSubscriptionEmailTemplate.Create +func (o DistrictSubscriptionEmailTemplate) Build() *models.DistrictSubscriptionEmail { + m := &models.DistrictSubscriptionEmail{} + + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } + if o.EmailContactAddress != nil { + m.EmailContactAddress = o.EmailContactAddress() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.DistrictSubscriptionEmailSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use DistrictSubscriptionEmailTemplate.CreateMany +func (o DistrictSubscriptionEmailTemplate) BuildMany(number int) models.DistrictSubscriptionEmailSlice { + m := make(models.DistrictSubscriptionEmailSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableDistrictSubscriptionEmail(m *models.DistrictSubscriptionEmailSetter) { + if !(m.OrganizationID.IsValue()) { + val := random_int32(nil) + m.OrganizationID = omit.From(val) + } + if !(m.EmailContactAddress.IsValue()) { + val := random_string(nil) + m.EmailContactAddress = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.DistrictSubscriptionEmail +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *DistrictSubscriptionEmailTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.DistrictSubscriptionEmail) error { + var err error + + return err +} + +// Create builds a districtSubscriptionEmail and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *DistrictSubscriptionEmailTemplate) Create(ctx context.Context, exec bob.Executor) (*models.DistrictSubscriptionEmail, error) { + var err error + opt := o.BuildSetter() + ensureCreatableDistrictSubscriptionEmail(opt) + + if o.r.EmailContactAddressEmailContact == nil { + DistrictSubscriptionEmailMods.WithNewEmailContactAddressEmailContact().Apply(ctx, o) + } + + var rel0 *models.CommsEmailContact + + if o.r.EmailContactAddressEmailContact.o.alreadyPersisted { + rel0 = o.r.EmailContactAddressEmailContact.o.Build() + } else { + rel0, err = o.r.EmailContactAddressEmailContact.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.EmailContactAddress = omit.From(rel0.Address) + + if o.r.Organization == nil { + DistrictSubscriptionEmailMods.WithNewOrganization().Apply(ctx, o) + } + + var rel1 *models.Organization + + if o.r.Organization.o.alreadyPersisted { + rel1 = o.r.Organization.o.Build() + } else { + rel1, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.OrganizationID = omit.From(rel1.ID) + + m, err := models.DistrictSubscriptionEmails.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.EmailContactAddressEmailContact = rel0 + m.R.Organization = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a districtSubscriptionEmail and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *DistrictSubscriptionEmailTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.DistrictSubscriptionEmail { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a districtSubscriptionEmail and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *DistrictSubscriptionEmailTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.DistrictSubscriptionEmail { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple districtSubscriptionEmails and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o DistrictSubscriptionEmailTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.DistrictSubscriptionEmailSlice, error) { + var err error + m := make(models.DistrictSubscriptionEmailSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple districtSubscriptionEmails and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o DistrictSubscriptionEmailTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.DistrictSubscriptionEmailSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple districtSubscriptionEmails and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o DistrictSubscriptionEmailTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.DistrictSubscriptionEmailSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// DistrictSubscriptionEmail has methods that act as mods for the DistrictSubscriptionEmailTemplate +var DistrictSubscriptionEmailMods districtSubscriptionEmailMods + +type districtSubscriptionEmailMods struct{} + +func (m districtSubscriptionEmailMods) RandomizeAllColumns(f *faker.Faker) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModSlice{ + DistrictSubscriptionEmailMods.RandomOrganizationID(f), + DistrictSubscriptionEmailMods.RandomEmailContactAddress(f), + } +} + +// Set the model columns to this value +func (m districtSubscriptionEmailMods) OrganizationID(val int32) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.OrganizationID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m districtSubscriptionEmailMods) OrganizationIDFunc(f func() int32) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m districtSubscriptionEmailMods) UnsetOrganizationID() DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m districtSubscriptionEmailMods) RandomOrganizationID(f *faker.Faker) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.OrganizationID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m districtSubscriptionEmailMods) EmailContactAddress(val string) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.EmailContactAddress = func() string { return val } + }) +} + +// Set the Column from the function +func (m districtSubscriptionEmailMods) EmailContactAddressFunc(f func() string) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.EmailContactAddress = f + }) +} + +// Clear any values for the column +func (m districtSubscriptionEmailMods) UnsetEmailContactAddress() DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.EmailContactAddress = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m districtSubscriptionEmailMods) RandomEmailContactAddress(f *faker.Faker) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(_ context.Context, o *DistrictSubscriptionEmailTemplate) { + o.EmailContactAddress = func() string { + return random_string(f) + } + }) +} + +func (m districtSubscriptionEmailMods) WithParentsCascading() DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + if isDone, _ := districtSubscriptionEmailWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = districtSubscriptionEmailWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewCommsEmailContactWithContext(ctx, CommsEmailContactMods.WithParentsCascading()) + m.WithEmailContactAddressEmailContact(related).Apply(ctx, o) + } + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } + }) +} + +func (m districtSubscriptionEmailMods) WithEmailContactAddressEmailContact(rel *CommsEmailContactTemplate) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + o.r.EmailContactAddressEmailContact = &districtSubscriptionEmailREmailContactAddressEmailContactR{ + o: rel, + } + }) +} + +func (m districtSubscriptionEmailMods) WithNewEmailContactAddressEmailContact(mods ...CommsEmailContactMod) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + related := o.f.NewCommsEmailContactWithContext(ctx, mods...) + + m.WithEmailContactAddressEmailContact(related).Apply(ctx, o) + }) +} + +func (m districtSubscriptionEmailMods) WithExistingEmailContactAddressEmailContact(em *models.CommsEmailContact) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + o.r.EmailContactAddressEmailContact = &districtSubscriptionEmailREmailContactAddressEmailContactR{ + o: o.f.FromExistingCommsEmailContact(em), + } + }) +} + +func (m districtSubscriptionEmailMods) WithoutEmailContactAddressEmailContact() DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + o.r.EmailContactAddressEmailContact = nil + }) +} + +func (m districtSubscriptionEmailMods) WithOrganization(rel *OrganizationTemplate) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + o.r.Organization = &districtSubscriptionEmailROrganizationR{ + o: rel, + } + }) +} + +func (m districtSubscriptionEmailMods) WithNewOrganization(mods ...OrganizationMod) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m districtSubscriptionEmailMods) WithExistingOrganization(em *models.Organization) DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + o.r.Organization = &districtSubscriptionEmailROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m districtSubscriptionEmailMods) WithoutOrganization() DistrictSubscriptionEmailMod { + return DistrictSubscriptionEmailModFunc(func(ctx context.Context, o *DistrictSubscriptionEmailTemplate) { + o.r.Organization = nil + }) +} diff --git a/db/factory/district_subscription_phone.bob.go b/db/factory/district_subscription_phone.bob.go new file mode 100644 index 00000000..225d1a00 --- /dev/null +++ b/db/factory/district_subscription_phone.bob.go @@ -0,0 +1,431 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + + "github.com/Gleipnir-Technology/bob" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" +) + +type DistrictSubscriptionPhoneMod interface { + Apply(context.Context, *DistrictSubscriptionPhoneTemplate) +} + +type DistrictSubscriptionPhoneModFunc func(context.Context, *DistrictSubscriptionPhoneTemplate) + +func (f DistrictSubscriptionPhoneModFunc) Apply(ctx context.Context, n *DistrictSubscriptionPhoneTemplate) { + f(ctx, n) +} + +type DistrictSubscriptionPhoneModSlice []DistrictSubscriptionPhoneMod + +func (mods DistrictSubscriptionPhoneModSlice) Apply(ctx context.Context, n *DistrictSubscriptionPhoneTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// DistrictSubscriptionPhoneTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type DistrictSubscriptionPhoneTemplate struct { + OrganizationID func() int32 + PhoneE164 func() string + + r districtSubscriptionPhoneR + f *Factory + + alreadyPersisted bool +} + +type districtSubscriptionPhoneR struct { + Organization *districtSubscriptionPhoneROrganizationR + PhoneE164Phone *districtSubscriptionPhoneRPhoneE164PhoneR +} + +type districtSubscriptionPhoneROrganizationR struct { + o *OrganizationTemplate +} +type districtSubscriptionPhoneRPhoneE164PhoneR struct { + o *CommsPhoneTemplate +} + +// Apply mods to the DistrictSubscriptionPhoneTemplate +func (o *DistrictSubscriptionPhoneTemplate) Apply(ctx context.Context, mods ...DistrictSubscriptionPhoneMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.DistrictSubscriptionPhone +// according to the relationships in the template. Nothing is inserted into the db +func (t DistrictSubscriptionPhoneTemplate) setModelRels(o *models.DistrictSubscriptionPhone) { + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + o.OrganizationID = rel.ID // h2 + o.R.Organization = rel + } + + if t.r.PhoneE164Phone != nil { + rel := t.r.PhoneE164Phone.o.Build() + o.PhoneE164 = rel.E164 // h2 + o.R.PhoneE164Phone = rel + } +} + +// BuildSetter returns an *models.DistrictSubscriptionPhoneSetter +// this does nothing with the relationship templates +func (o DistrictSubscriptionPhoneTemplate) BuildSetter() *models.DistrictSubscriptionPhoneSetter { + m := &models.DistrictSubscriptionPhoneSetter{} + + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omit.From(val) + } + if o.PhoneE164 != nil { + val := o.PhoneE164() + m.PhoneE164 = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.DistrictSubscriptionPhoneSetter +// this does nothing with the relationship templates +func (o DistrictSubscriptionPhoneTemplate) BuildManySetter(number int) []*models.DistrictSubscriptionPhoneSetter { + m := make([]*models.DistrictSubscriptionPhoneSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.DistrictSubscriptionPhone +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use DistrictSubscriptionPhoneTemplate.Create +func (o DistrictSubscriptionPhoneTemplate) Build() *models.DistrictSubscriptionPhone { + m := &models.DistrictSubscriptionPhone{} + + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } + if o.PhoneE164 != nil { + m.PhoneE164 = o.PhoneE164() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.DistrictSubscriptionPhoneSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use DistrictSubscriptionPhoneTemplate.CreateMany +func (o DistrictSubscriptionPhoneTemplate) BuildMany(number int) models.DistrictSubscriptionPhoneSlice { + m := make(models.DistrictSubscriptionPhoneSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableDistrictSubscriptionPhone(m *models.DistrictSubscriptionPhoneSetter) { + if !(m.OrganizationID.IsValue()) { + val := random_int32(nil) + m.OrganizationID = omit.From(val) + } + if !(m.PhoneE164.IsValue()) { + val := random_string(nil) + m.PhoneE164 = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.DistrictSubscriptionPhone +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *DistrictSubscriptionPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.DistrictSubscriptionPhone) error { + var err error + + return err +} + +// Create builds a districtSubscriptionPhone and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *DistrictSubscriptionPhoneTemplate) Create(ctx context.Context, exec bob.Executor) (*models.DistrictSubscriptionPhone, error) { + var err error + opt := o.BuildSetter() + ensureCreatableDistrictSubscriptionPhone(opt) + + if o.r.Organization == nil { + DistrictSubscriptionPhoneMods.WithNewOrganization().Apply(ctx, o) + } + + var rel0 *models.Organization + + if o.r.Organization.o.alreadyPersisted { + rel0 = o.r.Organization.o.Build() + } else { + rel0, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.OrganizationID = omit.From(rel0.ID) + + if o.r.PhoneE164Phone == nil { + DistrictSubscriptionPhoneMods.WithNewPhoneE164Phone().Apply(ctx, o) + } + + var rel1 *models.CommsPhone + + if o.r.PhoneE164Phone.o.alreadyPersisted { + rel1 = o.r.PhoneE164Phone.o.Build() + } else { + rel1, err = o.r.PhoneE164Phone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.PhoneE164 = omit.From(rel1.E164) + + m, err := models.DistrictSubscriptionPhones.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.Organization = rel0 + m.R.PhoneE164Phone = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a districtSubscriptionPhone and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *DistrictSubscriptionPhoneTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.DistrictSubscriptionPhone { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a districtSubscriptionPhone and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *DistrictSubscriptionPhoneTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.DistrictSubscriptionPhone { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple districtSubscriptionPhones and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o DistrictSubscriptionPhoneTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.DistrictSubscriptionPhoneSlice, error) { + var err error + m := make(models.DistrictSubscriptionPhoneSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple districtSubscriptionPhones and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o DistrictSubscriptionPhoneTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.DistrictSubscriptionPhoneSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple districtSubscriptionPhones and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o DistrictSubscriptionPhoneTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.DistrictSubscriptionPhoneSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// DistrictSubscriptionPhone has methods that act as mods for the DistrictSubscriptionPhoneTemplate +var DistrictSubscriptionPhoneMods districtSubscriptionPhoneMods + +type districtSubscriptionPhoneMods struct{} + +func (m districtSubscriptionPhoneMods) RandomizeAllColumns(f *faker.Faker) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModSlice{ + DistrictSubscriptionPhoneMods.RandomOrganizationID(f), + DistrictSubscriptionPhoneMods.RandomPhoneE164(f), + } +} + +// Set the model columns to this value +func (m districtSubscriptionPhoneMods) OrganizationID(val int32) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.OrganizationID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m districtSubscriptionPhoneMods) OrganizationIDFunc(f func() int32) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m districtSubscriptionPhoneMods) UnsetOrganizationID() DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m districtSubscriptionPhoneMods) RandomOrganizationID(f *faker.Faker) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.OrganizationID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m districtSubscriptionPhoneMods) PhoneE164(val string) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.PhoneE164 = func() string { return val } + }) +} + +// Set the Column from the function +func (m districtSubscriptionPhoneMods) PhoneE164Func(f func() string) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.PhoneE164 = f + }) +} + +// Clear any values for the column +func (m districtSubscriptionPhoneMods) UnsetPhoneE164() DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.PhoneE164 = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m districtSubscriptionPhoneMods) RandomPhoneE164(f *faker.Faker) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(_ context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.PhoneE164 = func() string { + return random_string(f) + } + }) +} + +func (m districtSubscriptionPhoneMods) WithParentsCascading() DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + if isDone, _ := districtSubscriptionPhoneWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = districtSubscriptionPhoneWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithPhoneE164Phone(related).Apply(ctx, o) + } + }) +} + +func (m districtSubscriptionPhoneMods) WithOrganization(rel *OrganizationTemplate) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.r.Organization = &districtSubscriptionPhoneROrganizationR{ + o: rel, + } + }) +} + +func (m districtSubscriptionPhoneMods) WithNewOrganization(mods ...OrganizationMod) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m districtSubscriptionPhoneMods) WithExistingOrganization(em *models.Organization) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.r.Organization = &districtSubscriptionPhoneROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m districtSubscriptionPhoneMods) WithoutOrganization() DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.r.Organization = nil + }) +} + +func (m districtSubscriptionPhoneMods) WithPhoneE164Phone(rel *CommsPhoneTemplate) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.r.PhoneE164Phone = &districtSubscriptionPhoneRPhoneE164PhoneR{ + o: rel, + } + }) +} + +func (m districtSubscriptionPhoneMods) WithNewPhoneE164Phone(mods ...CommsPhoneMod) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithPhoneE164Phone(related).Apply(ctx, o) + }) +} + +func (m districtSubscriptionPhoneMods) WithExistingPhoneE164Phone(em *models.CommsPhone) DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.r.PhoneE164Phone = &districtSubscriptionPhoneRPhoneE164PhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m districtSubscriptionPhoneMods) WithoutPhoneE164Phone() DistrictSubscriptionPhoneMod { + return DistrictSubscriptionPhoneModFunc(func(ctx context.Context, o *DistrictSubscriptionPhoneTemplate) { + o.r.PhoneE164Phone = nil + }) +} diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index e5396b91..6a994e3f 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -54,6 +54,8 @@ type OrganizationTemplate struct { } type organizationR struct { + EmailContacts []*organizationREmailContactsR + Phones []*organizationRPhonesR Containerrelates []*organizationRContainerrelatesR Fieldscoutinglogs []*organizationRFieldscoutinglogsR Habitatrelates []*organizationRHabitatrelatesR @@ -92,6 +94,14 @@ type organizationR struct { User []*organizationRUserR } +type organizationREmailContactsR struct { + number int + o *CommsEmailContactTemplate +} +type organizationRPhonesR struct { + number int + o *CommsPhoneTemplate +} type organizationRContainerrelatesR struct { number int o *FieldseekerContainerrelateTemplate @@ -246,6 +256,30 @@ func (o *OrganizationTemplate) Apply(ctx context.Context, mods ...OrganizationMo // setModelRels creates and sets the relationships on *models.Organization // according to the relationships in the template. Nothing is inserted into the db func (t OrganizationTemplate) setModelRels(o *models.Organization) { + if t.r.EmailContacts != nil { + rel := models.CommsEmailContactSlice{} + for _, r := range t.r.EmailContacts { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.R.Organizations = append(rel.R.Organizations, o) + } + rel = append(rel, related...) + } + o.R.EmailContacts = rel + } + + if t.r.Phones != nil { + rel := models.CommsPhoneSlice{} + for _, r := range t.r.Phones { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.R.Organizations = append(rel.R.Organizations, o) + } + rel = append(rel, related...) + } + o.R.Phones = rel + } + if t.r.Containerrelates != nil { rel := models.FieldseekerContainerrelateSlice{} for _, r := range t.r.Containerrelates { @@ -831,6 +865,46 @@ func ensureCreatableOrganization(m *models.OrganizationSetter) { func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.Organization) error { var err error + isEmailContactsDone, _ := organizationRelEmailContactsCtx.Value(ctx) + if !isEmailContactsDone && o.r.EmailContacts != nil { + ctx = organizationRelEmailContactsCtx.WithValue(ctx, true) + for _, r := range o.r.EmailContacts { + if r.o.alreadyPersisted { + m.R.EmailContacts = append(m.R.EmailContacts, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachEmailContacts(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + isPhonesDone, _ := organizationRelPhonesCtx.Value(ctx) + if !isPhonesDone && o.r.Phones != nil { + ctx = organizationRelPhonesCtx.WithValue(ctx, true) + for _, r := range o.r.Phones { + if r.o.alreadyPersisted { + m.R.Phones = append(m.R.Phones, r.o.Build()) + } else { + rel1, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachPhones(ctx, exec, rel1...) + if err != nil { + return err + } + } + } + } + isContainerrelatesDone, _ := organizationRelContainerrelatesCtx.Value(ctx) if !isContainerrelatesDone && o.r.Containerrelates != nil { ctx = organizationRelContainerrelatesCtx.WithValue(ctx, true) @@ -838,12 +912,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Containerrelates = append(m.R.Containerrelates, r.o.Build()) } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) + rel2, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachContainerrelates(ctx, exec, rel0...) + err = m.AttachContainerrelates(ctx, exec, rel2...) if err != nil { return err } @@ -858,12 +932,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Fieldscoutinglogs = append(m.R.Fieldscoutinglogs, r.o.Build()) } else { - rel1, err := r.o.CreateMany(ctx, exec, r.number) + rel3, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachFieldscoutinglogs(ctx, exec, rel1...) + err = m.AttachFieldscoutinglogs(ctx, exec, rel3...) if err != nil { return err } @@ -878,12 +952,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Habitatrelates = append(m.R.Habitatrelates, r.o.Build()) } else { - rel2, err := r.o.CreateMany(ctx, exec, r.number) + rel4, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachHabitatrelates(ctx, exec, rel2...) + err = m.AttachHabitatrelates(ctx, exec, rel4...) if err != nil { return err } @@ -898,12 +972,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Inspectionsamples = append(m.R.Inspectionsamples, r.o.Build()) } else { - rel3, err := r.o.CreateMany(ctx, exec, r.number) + rel5, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachInspectionsamples(ctx, exec, rel3...) + err = m.AttachInspectionsamples(ctx, exec, rel5...) if err != nil { return err } @@ -918,12 +992,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Inspectionsampledetails = append(m.R.Inspectionsampledetails, r.o.Build()) } else { - rel4, err := r.o.CreateMany(ctx, exec, r.number) + rel6, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachInspectionsampledetails(ctx, exec, rel4...) + err = m.AttachInspectionsampledetails(ctx, exec, rel6...) if err != nil { return err } @@ -938,12 +1012,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Linelocations = append(m.R.Linelocations, r.o.Build()) } else { - rel5, err := r.o.CreateMany(ctx, exec, r.number) + rel7, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachLinelocations(ctx, exec, rel5...) + err = m.AttachLinelocations(ctx, exec, rel7...) if err != nil { return err } @@ -958,12 +1032,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Locationtrackings = append(m.R.Locationtrackings, r.o.Build()) } else { - rel6, err := r.o.CreateMany(ctx, exec, r.number) + rel8, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachLocationtrackings(ctx, exec, rel6...) + err = m.AttachLocationtrackings(ctx, exec, rel8...) if err != nil { return err } @@ -978,12 +1052,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Mosquitoinspections = append(m.R.Mosquitoinspections, r.o.Build()) } else { - rel7, err := r.o.CreateMany(ctx, exec, r.number) + rel9, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachMosquitoinspections(ctx, exec, rel7...) + err = m.AttachMosquitoinspections(ctx, exec, rel9...) if err != nil { return err } @@ -998,12 +1072,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Pointlocations = append(m.R.Pointlocations, r.o.Build()) } else { - rel8, err := r.o.CreateMany(ctx, exec, r.number) + rel10, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPointlocations(ctx, exec, rel8...) + err = m.AttachPointlocations(ctx, exec, rel10...) if err != nil { return err } @@ -1018,12 +1092,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Polygonlocations = append(m.R.Polygonlocations, r.o.Build()) } else { - rel9, err := r.o.CreateMany(ctx, exec, r.number) + rel11, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPolygonlocations(ctx, exec, rel9...) + err = m.AttachPolygonlocations(ctx, exec, rel11...) if err != nil { return err } @@ -1038,12 +1112,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Pools = append(m.R.Pools, r.o.Build()) } else { - rel10, err := r.o.CreateMany(ctx, exec, r.number) + rel12, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPools(ctx, exec, rel10...) + err = m.AttachPools(ctx, exec, rel12...) if err != nil { return err } @@ -1058,12 +1132,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Pooldetails = append(m.R.Pooldetails, r.o.Build()) } else { - rel11, err := r.o.CreateMany(ctx, exec, r.number) + rel13, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPooldetails(ctx, exec, rel11...) + err = m.AttachPooldetails(ctx, exec, rel13...) if err != nil { return err } @@ -1078,12 +1152,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Proposedtreatmentareas = append(m.R.Proposedtreatmentareas, r.o.Build()) } else { - rel12, err := r.o.CreateMany(ctx, exec, r.number) + rel14, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachProposedtreatmentareas(ctx, exec, rel12...) + err = m.AttachProposedtreatmentareas(ctx, exec, rel14...) if err != nil { return err } @@ -1098,12 +1172,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Qamosquitoinspections = append(m.R.Qamosquitoinspections, r.o.Build()) } else { - rel13, err := r.o.CreateMany(ctx, exec, r.number) + rel15, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachQamosquitoinspections(ctx, exec, rel13...) + err = m.AttachQamosquitoinspections(ctx, exec, rel15...) if err != nil { return err } @@ -1118,12 +1192,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Rodentlocations = append(m.R.Rodentlocations, r.o.Build()) } else { - rel14, err := r.o.CreateMany(ctx, exec, r.number) + rel16, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachRodentlocations(ctx, exec, rel14...) + err = m.AttachRodentlocations(ctx, exec, rel16...) if err != nil { return err } @@ -1138,12 +1212,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Samplecollections = append(m.R.Samplecollections, r.o.Build()) } else { - rel15, err := r.o.CreateMany(ctx, exec, r.number) + rel17, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachSamplecollections(ctx, exec, rel15...) + err = m.AttachSamplecollections(ctx, exec, rel17...) if err != nil { return err } @@ -1158,12 +1232,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Samplelocations = append(m.R.Samplelocations, r.o.Build()) } else { - rel16, err := r.o.CreateMany(ctx, exec, r.number) + rel18, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachSamplelocations(ctx, exec, rel16...) + err = m.AttachSamplelocations(ctx, exec, rel18...) if err != nil { return err } @@ -1178,12 +1252,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Servicerequests = append(m.R.Servicerequests, r.o.Build()) } else { - rel17, err := r.o.CreateMany(ctx, exec, r.number) + rel19, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachServicerequests(ctx, exec, rel17...) + err = m.AttachServicerequests(ctx, exec, rel19...) if err != nil { return err } @@ -1198,12 +1272,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Speciesabundances = append(m.R.Speciesabundances, r.o.Build()) } else { - rel18, err := r.o.CreateMany(ctx, exec, r.number) + rel20, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachSpeciesabundances(ctx, exec, rel18...) + err = m.AttachSpeciesabundances(ctx, exec, rel20...) if err != nil { return err } @@ -1218,12 +1292,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Stormdrains = append(m.R.Stormdrains, r.o.Build()) } else { - rel19, err := r.o.CreateMany(ctx, exec, r.number) + rel21, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachStormdrains(ctx, exec, rel19...) + err = m.AttachStormdrains(ctx, exec, rel21...) if err != nil { return err } @@ -1238,12 +1312,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Timecards = append(m.R.Timecards, r.o.Build()) } else { - rel20, err := r.o.CreateMany(ctx, exec, r.number) + rel22, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachTimecards(ctx, exec, rel20...) + err = m.AttachTimecards(ctx, exec, rel22...) if err != nil { return err } @@ -1258,12 +1332,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Trapdata = append(m.R.Trapdata, r.o.Build()) } else { - rel21, err := r.o.CreateMany(ctx, exec, r.number) + rel23, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachTrapdata(ctx, exec, rel21...) + err = m.AttachTrapdata(ctx, exec, rel23...) if err != nil { return err } @@ -1278,12 +1352,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Traplocations = append(m.R.Traplocations, r.o.Build()) } else { - rel22, err := r.o.CreateMany(ctx, exec, r.number) + rel24, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachTraplocations(ctx, exec, rel22...) + err = m.AttachTraplocations(ctx, exec, rel24...) if err != nil { return err } @@ -1298,12 +1372,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Treatments = append(m.R.Treatments, r.o.Build()) } else { - rel23, err := r.o.CreateMany(ctx, exec, r.number) + rel25, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachTreatments(ctx, exec, rel23...) + err = m.AttachTreatments(ctx, exec, rel25...) if err != nil { return err } @@ -1318,12 +1392,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Treatmentareas = append(m.R.Treatmentareas, r.o.Build()) } else { - rel24, err := r.o.CreateMany(ctx, exec, r.number) + rel26, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachTreatmentareas(ctx, exec, rel24...) + err = m.AttachTreatmentareas(ctx, exec, rel26...) if err != nil { return err } @@ -1338,12 +1412,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Zones = append(m.R.Zones, r.o.Build()) } else { - rel25, err := r.o.CreateMany(ctx, exec, r.number) + rel27, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachZones(ctx, exec, rel25...) + err = m.AttachZones(ctx, exec, rel27...) if err != nil { return err } @@ -1358,12 +1432,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Zones2s = append(m.R.Zones2s, r.o.Build()) } else { - rel26, err := r.o.CreateMany(ctx, exec, r.number) + rel28, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachZones2s(ctx, exec, rel26...) + err = m.AttachZones2s(ctx, exec, rel28...) if err != nil { return err } @@ -1378,12 +1452,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.FieldseekerSyncs = append(m.R.FieldseekerSyncs, r.o.Build()) } else { - rel27, err := r.o.CreateMany(ctx, exec, r.number) + rel29, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachFieldseekerSyncs(ctx, exec, rel27...) + err = m.AttachFieldseekerSyncs(ctx, exec, rel29...) if err != nil { return err } @@ -1398,12 +1472,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.H3Aggregations = append(m.R.H3Aggregations, r.o.Build()) } else { - rel28, err := r.o.CreateMany(ctx, exec, r.number) + rel30, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachH3Aggregations(ctx, exec, rel28...) + err = m.AttachH3Aggregations(ctx, exec, rel30...) if err != nil { return err } @@ -1418,12 +1492,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.NoteAudios = append(m.R.NoteAudios, r.o.Build()) } else { - rel29, err := r.o.CreateMany(ctx, exec, r.number) + rel31, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachNoteAudios(ctx, exec, rel29...) + err = m.AttachNoteAudios(ctx, exec, rel31...) if err != nil { return err } @@ -1438,12 +1512,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.NoteImages = append(m.R.NoteImages, r.o.Build()) } else { - rel30, err := r.o.CreateMany(ctx, exec, r.number) + rel32, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachNoteImages(ctx, exec, rel30...) + err = m.AttachNoteImages(ctx, exec, rel32...) if err != nil { return err } @@ -1457,12 +1531,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if o.r.ImportDistrictGidDistrict.o.alreadyPersisted { m.R.ImportDistrictGidDistrict = o.r.ImportDistrictGidDistrict.o.Build() } else { - var rel31 *models.ImportDistrict - rel31, err = o.r.ImportDistrictGidDistrict.o.Create(ctx, exec) + var rel33 *models.ImportDistrict + rel33, err = o.r.ImportDistrictGidDistrict.o.Create(ctx, exec) if err != nil { return err } - err = m.AttachImportDistrictGidDistrict(ctx, exec, rel31) + err = m.AttachImportDistrictGidDistrict(ctx, exec, rel33) if err != nil { return err } @@ -1477,12 +1551,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Nuisances = append(m.R.Nuisances, r.o.Build()) } else { - rel32, err := r.o.CreateMany(ctx, exec, r.number) + rel34, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachNuisances(ctx, exec, rel32...) + err = m.AttachNuisances(ctx, exec, rel34...) if err != nil { return err } @@ -1497,12 +1571,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.PublicreportPool = append(m.R.PublicreportPool, r.o.Build()) } else { - rel33, err := r.o.CreateMany(ctx, exec, r.number) + rel35, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPublicreportPool(ctx, exec, rel33...) + err = m.AttachPublicreportPool(ctx, exec, rel35...) if err != nil { return err } @@ -1517,12 +1591,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Quicks = append(m.R.Quicks, r.o.Build()) } else { - rel34, err := r.o.CreateMany(ctx, exec, r.number) + rel36, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachQuicks(ctx, exec, rel34...) + err = m.AttachQuicks(ctx, exec, rel36...) if err != nil { return err } @@ -1537,12 +1611,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.User = append(m.R.User, r.o.Build()) } else { - rel35, err := r.o.CreateMany(ctx, exec, r.number) + rel37, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachUser(ctx, exec, rel35...) + err = m.AttachUser(ctx, exec, rel37...) if err != nil { return err } @@ -2131,6 +2205,102 @@ func (m organizationMods) WithoutImportDistrictGidDistrict() OrganizationMod { }) } +func (m organizationMods) WithEmailContacts(number int, related *CommsEmailContactTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.EmailContacts = []*organizationREmailContactsR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewEmailContacts(number int, mods ...CommsEmailContactMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewCommsEmailContactWithContext(ctx, mods...) + m.WithEmailContacts(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddEmailContacts(number int, related *CommsEmailContactTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.EmailContacts = append(o.r.EmailContacts, &organizationREmailContactsR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewEmailContacts(number int, mods ...CommsEmailContactMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewCommsEmailContactWithContext(ctx, mods...) + m.AddEmailContacts(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingEmailContacts(existingModels ...*models.CommsEmailContact) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.EmailContacts = append(o.r.EmailContacts, &organizationREmailContactsR{ + o: o.f.FromExistingCommsEmailContact(em), + }) + } + }) +} + +func (m organizationMods) WithoutEmailContacts() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.EmailContacts = nil + }) +} + +func (m organizationMods) WithPhones(number int, related *CommsPhoneTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Phones = []*organizationRPhonesR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewPhones(number int, mods ...CommsPhoneMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + m.WithPhones(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddPhones(number int, related *CommsPhoneTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Phones = append(o.r.Phones, &organizationRPhonesR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewPhones(number int, mods ...CommsPhoneMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + m.AddPhones(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingPhones(existingModels ...*models.CommsPhone) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.Phones = append(o.r.Phones, &organizationRPhonesR{ + o: o.f.FromExistingCommsPhone(em), + }) + } + }) +} + +func (m organizationMods) WithoutPhones() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Phones = nil + }) +} + func (m organizationMods) WithContainerrelates(number int, related *FieldseekerContainerrelateTemplate) OrganizationMod { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { o.r.Containerrelates = []*organizationRContainerrelatesR{{ diff --git a/db/migrations/00048_comms_phone_subscribed.sql b/db/migrations/00048_comms_phone_subscribed.sql new file mode 100644 index 00000000..32f6feb5 --- /dev/null +++ b/db/migrations/00048_comms_phone_subscribed.sql @@ -0,0 +1,21 @@ +-- +goose Up +CREATE TYPE comms.PhoneStatusType AS ENUM ( + 'unconfirmed', + 'ok-to-send', + 'stopped' +); +ALTER TABLE comms.phone ADD COLUMN status comms.PhoneStatusType; +UPDATE comms.phone SET status = 'unconfirmed'; +ALTER TABLE comms.phone ALTER COLUMN status SET NOT NULL; +UPDATE comms.phone SET is_subscribed = FALSE; +ALTER TABLE comms.phone ALTER COLUMN is_subscribed SET NOT NULL; +CREATE TABLE district_subscription_email ( + organization_id INTEGER NOT NULL REFERENCES organization(id), + email_contact_address TEXT NOT NULL REFERENCES comms.email_contact(address), + PRIMARY KEY(organization_id, email_contact_address) +); +CREATE TABLE district_subscription_phone ( + organization_id INTEGER NOT NULL REFERENCES organization(id), + phone_e164 TEXT NOT NULL REFERENCES comms.phone(e164), + PRIMARY KEY(organization_id, phone_e164) +); diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index e6c2439d..f34bc98e 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -40,6 +40,8 @@ type joins[Q dialect.Joinable] struct { CommsPhones joinSet[commsPhoneJoins[Q]] CommsTextJobs joinSet[commsTextJobJoins[Q]] CommsTextLogs joinSet[commsTextLogJoins[Q]] + DistrictSubscriptionEmails joinSet[districtSubscriptionEmailJoins[Q]] + DistrictSubscriptionPhones joinSet[districtSubscriptionPhoneJoins[Q]] FieldseekerContainerrelates joinSet[fieldseekerContainerrelateJoins[Q]] FieldseekerFieldscoutinglogs joinSet[fieldseekerFieldscoutinglogJoins[Q]] FieldseekerHabitatrelates joinSet[fieldseekerHabitatrelateJoins[Q]] @@ -107,6 +109,8 @@ func getJoins[Q dialect.Joinable]() joins[Q] { CommsPhones: buildJoinSet[commsPhoneJoins[Q]](CommsPhones.Columns, buildCommsPhoneJoins), CommsTextJobs: buildJoinSet[commsTextJobJoins[Q]](CommsTextJobs.Columns, buildCommsTextJobJoins), CommsTextLogs: buildJoinSet[commsTextLogJoins[Q]](CommsTextLogs.Columns, buildCommsTextLogJoins), + DistrictSubscriptionEmails: buildJoinSet[districtSubscriptionEmailJoins[Q]](DistrictSubscriptionEmails.Columns, buildDistrictSubscriptionEmailJoins), + DistrictSubscriptionPhones: buildJoinSet[districtSubscriptionPhoneJoins[Q]](DistrictSubscriptionPhones.Columns, buildDistrictSubscriptionPhoneJoins), FieldseekerContainerrelates: buildJoinSet[fieldseekerContainerrelateJoins[Q]](FieldseekerContainerrelates.Columns, buildFieldseekerContainerrelateJoins), FieldseekerFieldscoutinglogs: buildJoinSet[fieldseekerFieldscoutinglogJoins[Q]](FieldseekerFieldscoutinglogs.Columns, buildFieldseekerFieldscoutinglogJoins), FieldseekerHabitatrelates: buildJoinSet[fieldseekerHabitatrelateJoins[Q]](FieldseekerHabitatrelates.Columns, buildFieldseekerHabitatrelateJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 708a4e90..b0d4b698 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -25,6 +25,8 @@ type preloaders struct { CommsPhone commsPhonePreloader CommsTextJob commsTextJobPreloader CommsTextLog commsTextLogPreloader + DistrictSubscriptionEmail districtSubscriptionEmailPreloader + DistrictSubscriptionPhone districtSubscriptionPhonePreloader FieldseekerContainerrelate fieldseekerContainerrelatePreloader FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogPreloader FieldseekerHabitatrelate fieldseekerHabitatrelatePreloader @@ -84,6 +86,8 @@ func getPreloaders() preloaders { CommsPhone: buildCommsPhonePreloader(), CommsTextJob: buildCommsTextJobPreloader(), CommsTextLog: buildCommsTextLogPreloader(), + DistrictSubscriptionEmail: buildDistrictSubscriptionEmailPreloader(), + DistrictSubscriptionPhone: buildDistrictSubscriptionPhonePreloader(), FieldseekerContainerrelate: buildFieldseekerContainerrelatePreloader(), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogPreloader(), FieldseekerHabitatrelate: buildFieldseekerHabitatrelatePreloader(), @@ -149,6 +153,8 @@ type thenLoaders[Q orm.Loadable] struct { CommsPhone commsPhoneThenLoader[Q] CommsTextJob commsTextJobThenLoader[Q] CommsTextLog commsTextLogThenLoader[Q] + DistrictSubscriptionEmail districtSubscriptionEmailThenLoader[Q] + DistrictSubscriptionPhone districtSubscriptionPhoneThenLoader[Q] FieldseekerContainerrelate fieldseekerContainerrelateThenLoader[Q] FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogThenLoader[Q] FieldseekerHabitatrelate fieldseekerHabitatrelateThenLoader[Q] @@ -208,6 +214,8 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { CommsPhone: buildCommsPhoneThenLoader[Q](), CommsTextJob: buildCommsTextJobThenLoader[Q](), CommsTextLog: buildCommsTextLogThenLoader[Q](), + DistrictSubscriptionEmail: buildDistrictSubscriptionEmailThenLoader[Q](), + DistrictSubscriptionPhone: buildDistrictSubscriptionPhoneThenLoader[Q](), FieldseekerContainerrelate: buildFieldseekerContainerrelateThenLoader[Q](), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogThenLoader[Q](), FieldseekerHabitatrelate: buildFieldseekerHabitatrelateThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 2eaec9d9..171db52c 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -25,6 +25,8 @@ func Where[Q psql.Filterable]() struct { CommsPhones commsPhoneWhere[Q] CommsTextJobs commsTextJobWhere[Q] CommsTextLogs commsTextLogWhere[Q] + DistrictSubscriptionEmails districtSubscriptionEmailWhere[Q] + DistrictSubscriptionPhones districtSubscriptionPhoneWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -90,6 +92,8 @@ func Where[Q psql.Filterable]() struct { CommsPhones commsPhoneWhere[Q] CommsTextJobs commsTextJobWhere[Q] CommsTextLogs commsTextLogWhere[Q] + DistrictSubscriptionEmails districtSubscriptionEmailWhere[Q] + DistrictSubscriptionPhones districtSubscriptionPhoneWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -154,6 +158,8 @@ func Where[Q psql.Filterable]() struct { CommsPhones: buildCommsPhoneWhere[Q](CommsPhones.Columns), CommsTextJobs: buildCommsTextJobWhere[Q](CommsTextJobs.Columns), CommsTextLogs: buildCommsTextLogWhere[Q](CommsTextLogs.Columns), + DistrictSubscriptionEmails: buildDistrictSubscriptionEmailWhere[Q](DistrictSubscriptionEmails.Columns), + DistrictSubscriptionPhones: buildDistrictSubscriptionPhoneWhere[Q](DistrictSubscriptionPhones.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), FieldseekerFieldscoutinglogs: buildFieldseekerFieldscoutinglogWhere[Q](FieldseekerFieldscoutinglogs.Columns), FieldseekerHabitatrelates: buildFieldseekerHabitatrelateWhere[Q](FieldseekerHabitatrelates.Columns), diff --git a/db/models/comms.email_contact.bob.go b/db/models/comms.email_contact.bob.go index ff97fc8d..0aa83ed7 100644 --- a/db/models/comms.email_contact.bob.go +++ b/db/models/comms.email_contact.bob.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "strconv" "github.com/Gleipnir-Technology/bob" "github.com/Gleipnir-Technology/bob/dialect/psql" @@ -19,6 +20,7 @@ import ( "github.com/Gleipnir-Technology/bob/orm" "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" + "github.com/stephenafamo/scan" ) // CommsEmailContact is an object representing the database table. @@ -46,6 +48,7 @@ type CommsEmailContactsQuery = *psql.ViewQuery[*CommsEmailContact, CommsEmailCon // commsEmailContactR is where relationships are stored. type commsEmailContactR struct { DestinationEmailLogs CommsEmailLogSlice // comms.email_log.email_log_destination_fkey + Organizations OrganizationSlice // district_subscription_email.district_subscription_email_email_contact_address_fkeydistrict_subscription_email.district_subscription_email_organization_id_fkey } func buildCommsEmailContactColumns(alias string) commsEmailContactColumns { @@ -440,6 +443,35 @@ func (os CommsEmailContactSlice) DestinationEmailLogs(mods ...bob.Mod[*dialect.S )...) } +// Organizations starts a query for related objects on organization +func (o *CommsEmailContact) Organizations(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionEmails.NameAs()).On( + Organizations.Columns.ID.EQ(DistrictSubscriptionEmails.Columns.OrganizationID)), + sm.Where(DistrictSubscriptionEmails.Columns.EmailContactAddress.EQ(psql.Arg(o.Address))), + )...) +} + +func (os CommsEmailContactSlice) Organizations(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkAddress := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkAddress = append(pkAddress, o.Address) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkAddress), "text[]")), + )) + + return Organizations.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionEmails.NameAs()).On( + Organizations.Columns.ID.EQ(DistrictSubscriptionEmails.Columns.OrganizationID), + ), + sm.Where(psql.Group(DistrictSubscriptionEmails.Columns.EmailContactAddress).OP("IN", PKArgExpr)), + )...) +} + func insertCommsEmailContactDestinationEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmailContact0 *CommsEmailContact) (CommsEmailLogSlice, error) { for i := range commsEmailLogs1 { commsEmailLogs1[i].Destination = omit.From(commsEmailContact0.Address) @@ -508,6 +540,71 @@ func (commsEmailContact0 *CommsEmailContact) AttachDestinationEmailLogs(ctx cont return nil } +func attachCommsEmailContactOrganizations0(ctx context.Context, exec bob.Executor, count int, commsEmailContact0 *CommsEmailContact, organizations2 OrganizationSlice) (DistrictSubscriptionEmailSlice, error) { + setters := make([]*DistrictSubscriptionEmailSetter, count) + for i := range count { + setters[i] = &DistrictSubscriptionEmailSetter{ + EmailContactAddress: omit.From(commsEmailContact0.Address), + OrganizationID: omit.From(organizations2[i].ID), + } + } + + districtSubscriptionEmails1, err := DistrictSubscriptionEmails.Insert(bob.ToMods(setters...)).All(ctx, exec) + if err != nil { + return nil, fmt.Errorf("attachCommsEmailContactOrganizations0: %w", err) + } + + return districtSubscriptionEmails1, nil +} + +func (commsEmailContact0 *CommsEmailContact) InsertOrganizations(ctx context.Context, exec bob.Executor, related ...*OrganizationSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + inserted, err := Organizations.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + organizations2 := OrganizationSlice(inserted) + + _, err = attachCommsEmailContactOrganizations0(ctx, exec, len(related), commsEmailContact0, organizations2) + if err != nil { + return err + } + + commsEmailContact0.R.Organizations = append(commsEmailContact0.R.Organizations, organizations2...) + + for _, rel := range organizations2 { + rel.R.EmailContacts = append(rel.R.EmailContacts, commsEmailContact0) + } + return nil +} + +func (commsEmailContact0 *CommsEmailContact) AttachOrganizations(ctx context.Context, exec bob.Executor, related ...*Organization) error { + if len(related) == 0 { + return nil + } + + var err error + organizations2 := OrganizationSlice(related) + + _, err = attachCommsEmailContactOrganizations0(ctx, exec, len(related), commsEmailContact0, organizations2) + if err != nil { + return err + } + + commsEmailContact0.R.Organizations = append(commsEmailContact0.R.Organizations, organizations2...) + + for _, rel := range related { + rel.R.EmailContacts = append(rel.R.EmailContacts, commsEmailContact0) + } + + return nil +} + type commsEmailContactWhere[Q psql.Filterable] struct { Address psql.WhereMod[Q, string] Confirmed psql.WhereMod[Q, bool] @@ -548,6 +645,20 @@ func (o *CommsEmailContact) Preload(name string, retrieved any) error { } } return nil + case "Organizations": + rels, ok := retrieved.(OrganizationSlice) + if !ok { + return fmt.Errorf("commsEmailContact cannot load %T as %q", retrieved, name) + } + + o.R.Organizations = rels + + for _, rel := range rels { + if rel != nil { + rel.R.EmailContacts = CommsEmailContactSlice{o} + } + } + return nil default: return fmt.Errorf("commsEmailContact has no relationship %q", name) } @@ -561,12 +672,16 @@ func buildCommsEmailContactPreloader() commsEmailContactPreloader { type commsEmailContactThenLoader[Q orm.Loadable] struct { DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organizations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsEmailContactThenLoader[Q orm.Loadable]() commsEmailContactThenLoader[Q] { type DestinationEmailLogsLoadInterface interface { LoadDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type OrganizationsLoadInterface interface { + LoadOrganizations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return commsEmailContactThenLoader[Q]{ DestinationEmailLogs: thenLoadBuilder[Q]( @@ -575,6 +690,12 @@ func buildCommsEmailContactThenLoader[Q orm.Loadable]() commsEmailContactThenLoa return retrieved.LoadDestinationEmailLogs(ctx, exec, mods...) }, ), + Organizations: thenLoadBuilder[Q]( + "Organizations", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganizations(ctx, exec, mods...) + }, + ), } } @@ -639,9 +760,91 @@ func (os CommsEmailContactSlice) LoadDestinationEmailLogs(ctx context.Context, e return nil } +// LoadOrganizations loads the commsEmailContact's Organizations into the .R struct +func (o *CommsEmailContact) LoadOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organizations = nil + + related, err := o.Organizations(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.EmailContacts = CommsEmailContactSlice{o} + } + + o.R.Organizations = related + return nil +} + +// LoadOrganizations loads the commsEmailContact's Organizations into the .R struct +func (os CommsEmailContactSlice) LoadOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(Organizations.Columns)) + } + + q := os.Organizations(append( + mods, + sm.Columns(DistrictSubscriptionEmails.Columns.EmailContactAddress.As("related_comms.email_contact.Address")), + )...) + + AddressSlice := []string{} + + mapper := scan.Mod(scan.StructMapper[*Organization](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + AddressSlice = append(AddressSlice, *new(string)) + row.ScheduleScanByName("related_comms.email_contact.Address", &AddressSlice[len(AddressSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + organizations, err := bob.Allx[bob.SliceTransformer[*Organization, OrganizationSlice]](ctx, exec, q, mapper) + if err != nil { + return err + } + + for _, o := range os { + o.R.Organizations = nil + } + + for _, o := range os { + for i, rel := range organizations { + if !(o.Address == AddressSlice[i]) { + continue + } + + rel.R.EmailContacts = append(rel.R.EmailContacts, o) + + o.R.Organizations = append(o.R.Organizations, rel) + } + } + + return nil +} + // commsEmailContactC is where relationship counts are stored. type commsEmailContactC struct { DestinationEmailLogs *int64 + Organizations *int64 } // PreloadCount sets a count in the C struct by name @@ -653,12 +856,15 @@ func (o *CommsEmailContact) PreloadCount(name string, count int64) error { switch name { case "DestinationEmailLogs": o.C.DestinationEmailLogs = &count + case "Organizations": + o.C.Organizations = &count } return nil } type commsEmailContactCountPreloader struct { DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Organizations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildCommsEmailContactCountPreloader() commsEmailContactCountPreloader { @@ -680,17 +886,41 @@ func buildCommsEmailContactCountPreloader() commsEmailContactCountPreloader { return psql.Group(psql.Select(subqueryMods...).Expression) }) }, + Organizations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsEmailContact]("Organizations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsEmailContacts.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(DistrictSubscriptionEmails.Name()), + sm.Where(psql.Quote(DistrictSubscriptionEmails.Alias(), "email_contact_address").EQ(psql.Quote(parent, "address"))), + sm.InnerJoin(Organizations.Name()).On( + psql.Quote(Organizations.Alias(), "id").EQ(psql.Quote(DistrictSubscriptionEmails.Alias(), "organization_id")), + ), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, } } type commsEmailContactCountThenLoader[Q orm.Loadable] struct { DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organizations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsEmailContactCountThenLoader[Q orm.Loadable]() commsEmailContactCountThenLoader[Q] { type DestinationEmailLogsCountInterface interface { LoadCountDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type OrganizationsCountInterface interface { + LoadCountOrganizations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return commsEmailContactCountThenLoader[Q]{ DestinationEmailLogs: countThenLoadBuilder[Q]( @@ -699,6 +929,12 @@ func buildCommsEmailContactCountThenLoader[Q orm.Loadable]() commsEmailContactCo return retrieved.LoadCountDestinationEmailLogs(ctx, exec, mods...) }, ), + Organizations: countThenLoadBuilder[Q]( + "Organizations", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountOrganizations(ctx, exec, mods...) + }, + ), } } @@ -732,9 +968,40 @@ func (os CommsEmailContactSlice) LoadCountDestinationEmailLogs(ctx context.Conte return nil } +// LoadCountOrganizations loads the count of Organizations into the C struct +func (o *CommsEmailContact) LoadCountOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Organizations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Organizations = &count + return nil +} + +// LoadCountOrganizations loads the count of Organizations for a slice +func (os CommsEmailContactSlice) LoadCountOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountOrganizations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type commsEmailContactJoins[Q dialect.Joinable] struct { typ string DestinationEmailLogs modAs[Q, commsEmailLogColumns] + Organizations modAs[Q, organizationColumns] } func (j commsEmailContactJoins[Q]) aliasedAs(alias string) commsEmailContactJoins[Q] { @@ -755,6 +1022,28 @@ func buildCommsEmailContactJoins[Q dialect.Joinable](cols commsEmailContactColum )) } + return mods + }, + }, + Organizations: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) + + { + to := DistrictSubscriptionEmails.Columns.AliasedAs(DistrictSubscriptionEmails.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, DistrictSubscriptionEmails.Name().As(to.Alias())).On( + to.EmailContactAddress.EQ(cols.Address), + )) + } + { + cols := DistrictSubscriptionEmails.Columns.AliasedAs(DistrictSubscriptionEmails.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + return mods }, }, diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index 70bc8a63..8116cf4d 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "strconv" "github.com/Gleipnir-Technology/bob" "github.com/Gleipnir-Technology/bob/dialect/psql" @@ -18,15 +19,16 @@ import ( "github.com/Gleipnir-Technology/bob/mods" "github.com/Gleipnir-Technology/bob/orm" "github.com/Gleipnir-Technology/bob/types/pgtypes" - "github.com/aarondl/opt/null" + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/omit" - "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/scan" ) // CommsPhone is an object representing the database table. type CommsPhone struct { - E164 string `db:"e164,pk" ` - IsSubscribed null.Val[bool] `db:"is_subscribed" ` + E164 string `db:"e164,pk" ` + IsSubscribed bool `db:"is_subscribed" ` + Status enums.CommsPhonestatustype `db:"status" ` R commsPhoneR `db:"-" ` @@ -48,16 +50,18 @@ type commsPhoneR struct { DestinationTextJobs CommsTextJobSlice // comms.text_job.text_job_destination_fkey DestinationTextLogs CommsTextLogSlice // comms.text_log.text_log_destination_fkey SourceTextLogs CommsTextLogSlice // comms.text_log.text_log_source_fkey + Organizations OrganizationSlice // district_subscription_phone.district_subscription_phone_organization_id_fkeydistrict_subscription_phone.district_subscription_phone_phone_e164_fkey } func buildCommsPhoneColumns(alias string) commsPhoneColumns { return commsPhoneColumns{ ColumnsExpr: expr.NewColumnsExpr( - "e164", "is_subscribed", + "e164", "is_subscribed", "status", ).WithParent("comms.phone"), tableAlias: alias, E164: psql.Quote(alias, "e164"), IsSubscribed: psql.Quote(alias, "is_subscribed"), + Status: psql.Quote(alias, "status"), } } @@ -66,6 +70,7 @@ type commsPhoneColumns struct { tableAlias string E164 psql.Expression IsSubscribed psql.Expression + Status psql.Expression } func (c commsPhoneColumns) Alias() string { @@ -80,18 +85,22 @@ func (commsPhoneColumns) AliasedAs(alias string) commsPhoneColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommsPhoneSetter struct { - E164 omit.Val[string] `db:"e164,pk" ` - IsSubscribed omitnull.Val[bool] `db:"is_subscribed" ` + E164 omit.Val[string] `db:"e164,pk" ` + IsSubscribed omit.Val[bool] `db:"is_subscribed" ` + Status omit.Val[enums.CommsPhonestatustype] `db:"status" ` } func (s CommsPhoneSetter) SetColumns() []string { - vals := make([]string, 0, 2) + vals := make([]string, 0, 3) if s.E164.IsValue() { vals = append(vals, "e164") } - if !s.IsSubscribed.IsUnset() { + if s.IsSubscribed.IsValue() { vals = append(vals, "is_subscribed") } + if s.Status.IsValue() { + vals = append(vals, "status") + } return vals } @@ -99,8 +108,11 @@ func (s CommsPhoneSetter) Overwrite(t *CommsPhone) { if s.E164.IsValue() { t.E164 = s.E164.MustGet() } - if !s.IsSubscribed.IsUnset() { - t.IsSubscribed = s.IsSubscribed.MustGetNull() + if s.IsSubscribed.IsValue() { + t.IsSubscribed = s.IsSubscribed.MustGet() + } + if s.Status.IsValue() { + t.Status = s.Status.MustGet() } } @@ -110,19 +122,25 @@ func (s *CommsPhoneSetter) 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, 2) + vals := make([]bob.Expression, 3) if s.E164.IsValue() { vals[0] = psql.Arg(s.E164.MustGet()) } else { vals[0] = psql.Raw("DEFAULT") } - if !s.IsSubscribed.IsUnset() { - vals[1] = psql.Arg(s.IsSubscribed.MustGetNull()) + if s.IsSubscribed.IsValue() { + vals[1] = psql.Arg(s.IsSubscribed.MustGet()) } else { vals[1] = psql.Raw("DEFAULT") } + if s.Status.IsValue() { + vals[2] = psql.Arg(s.Status.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -132,7 +150,7 @@ func (s CommsPhoneSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsPhoneSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 2) + exprs := make([]bob.Expression, 0, 3) if s.E164.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -141,13 +159,20 @@ func (s CommsPhoneSetter) Expressions(prefix ...string) []bob.Expression { }}) } - if !s.IsSubscribed.IsUnset() { + if s.IsSubscribed.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "is_subscribed")...), psql.Arg(s.IsSubscribed), }}) } + if s.Status.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "status")...), + psql.Arg(s.Status), + }}) + } + return exprs } @@ -446,6 +471,35 @@ func (os CommsPhoneSlice) SourceTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) )...) } +// Organizations starts a query for related objects on organization +func (o *CommsPhone) Organizations(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionPhones.NameAs()).On( + Organizations.Columns.ID.EQ(DistrictSubscriptionPhones.Columns.OrganizationID)), + sm.Where(DistrictSubscriptionPhones.Columns.PhoneE164.EQ(psql.Arg(o.E164))), + )...) +} + +func (os CommsPhoneSlice) Organizations(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkE164 := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkE164 = append(pkE164, o.E164) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), + )) + + return Organizations.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionPhones.NameAs()).On( + Organizations.Columns.ID.EQ(DistrictSubscriptionPhones.Columns.OrganizationID), + ), + sm.Where(psql.Group(DistrictSubscriptionPhones.Columns.PhoneE164).OP("IN", PKArgExpr)), + )...) +} + func insertCommsPhoneDestinationTextJobs0(ctx context.Context, exec bob.Executor, commsTextJobs1 []*CommsTextJobSetter, commsPhone0 *CommsPhone) (CommsTextJobSlice, error) { for i := range commsTextJobs1 { commsTextJobs1[i].Destination = omit.From(commsPhone0.E164) @@ -650,9 +704,75 @@ func (commsPhone0 *CommsPhone) AttachSourceTextLogs(ctx context.Context, exec bo return nil } +func attachCommsPhoneOrganizations0(ctx context.Context, exec bob.Executor, count int, commsPhone0 *CommsPhone, organizations2 OrganizationSlice) (DistrictSubscriptionPhoneSlice, error) { + setters := make([]*DistrictSubscriptionPhoneSetter, count) + for i := range count { + setters[i] = &DistrictSubscriptionPhoneSetter{ + PhoneE164: omit.From(commsPhone0.E164), + OrganizationID: omit.From(organizations2[i].ID), + } + } + + districtSubscriptionPhones1, err := DistrictSubscriptionPhones.Insert(bob.ToMods(setters...)).All(ctx, exec) + if err != nil { + return nil, fmt.Errorf("attachCommsPhoneOrganizations0: %w", err) + } + + return districtSubscriptionPhones1, nil +} + +func (commsPhone0 *CommsPhone) InsertOrganizations(ctx context.Context, exec bob.Executor, related ...*OrganizationSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + inserted, err := Organizations.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + organizations2 := OrganizationSlice(inserted) + + _, err = attachCommsPhoneOrganizations0(ctx, exec, len(related), commsPhone0, organizations2) + if err != nil { + return err + } + + commsPhone0.R.Organizations = append(commsPhone0.R.Organizations, organizations2...) + + for _, rel := range organizations2 { + rel.R.Phones = append(rel.R.Phones, commsPhone0) + } + return nil +} + +func (commsPhone0 *CommsPhone) AttachOrganizations(ctx context.Context, exec bob.Executor, related ...*Organization) error { + if len(related) == 0 { + return nil + } + + var err error + organizations2 := OrganizationSlice(related) + + _, err = attachCommsPhoneOrganizations0(ctx, exec, len(related), commsPhone0, organizations2) + if err != nil { + return err + } + + commsPhone0.R.Organizations = append(commsPhone0.R.Organizations, organizations2...) + + for _, rel := range related { + rel.R.Phones = append(rel.R.Phones, commsPhone0) + } + + return nil +} + type commsPhoneWhere[Q psql.Filterable] struct { E164 psql.WhereMod[Q, string] - IsSubscribed psql.WhereNullMod[Q, bool] + IsSubscribed psql.WhereMod[Q, bool] + Status psql.WhereMod[Q, enums.CommsPhonestatustype] } func (commsPhoneWhere[Q]) AliasedAs(alias string) commsPhoneWhere[Q] { @@ -662,7 +782,8 @@ func (commsPhoneWhere[Q]) AliasedAs(alias string) commsPhoneWhere[Q] { func buildCommsPhoneWhere[Q psql.Filterable](cols commsPhoneColumns) commsPhoneWhere[Q] { return commsPhoneWhere[Q]{ E164: psql.Where[Q, string](cols.E164), - IsSubscribed: psql.WhereNull[Q, bool](cols.IsSubscribed), + IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), + Status: psql.Where[Q, enums.CommsPhonestatustype](cols.Status), } } @@ -714,6 +835,20 @@ func (o *CommsPhone) Preload(name string, retrieved any) error { } } return nil + case "Organizations": + rels, ok := retrieved.(OrganizationSlice) + if !ok { + return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) + } + + o.R.Organizations = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Phones = CommsPhoneSlice{o} + } + } + return nil default: return fmt.Errorf("commsPhone has no relationship %q", name) } @@ -729,6 +864,7 @@ type commsPhoneThenLoader[Q orm.Loadable] struct { DestinationTextJobs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organizations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { @@ -741,6 +877,9 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { type SourceTextLogsLoadInterface interface { LoadSourceTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type OrganizationsLoadInterface interface { + LoadOrganizations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return commsPhoneThenLoader[Q]{ DestinationTextJobs: thenLoadBuilder[Q]( @@ -761,6 +900,12 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { return retrieved.LoadSourceTextLogs(ctx, exec, mods...) }, ), + Organizations: thenLoadBuilder[Q]( + "Organizations", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganizations(ctx, exec, mods...) + }, + ), } } @@ -947,11 +1092,93 @@ func (os CommsPhoneSlice) LoadSourceTextLogs(ctx context.Context, exec bob.Execu return nil } +// LoadOrganizations loads the commsPhone's Organizations into the .R struct +func (o *CommsPhone) LoadOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organizations = nil + + related, err := o.Organizations(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Phones = CommsPhoneSlice{o} + } + + o.R.Organizations = related + return nil +} + +// LoadOrganizations loads the commsPhone's Organizations into the .R struct +func (os CommsPhoneSlice) LoadOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(Organizations.Columns)) + } + + q := os.Organizations(append( + mods, + sm.Columns(DistrictSubscriptionPhones.Columns.PhoneE164.As("related_comms.phone.E164")), + )...) + + E164Slice := []string{} + + mapper := scan.Mod(scan.StructMapper[*Organization](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + E164Slice = append(E164Slice, *new(string)) + row.ScheduleScanByName("related_comms.phone.E164", &E164Slice[len(E164Slice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + organizations, err := bob.Allx[bob.SliceTransformer[*Organization, OrganizationSlice]](ctx, exec, q, mapper) + if err != nil { + return err + } + + for _, o := range os { + o.R.Organizations = nil + } + + for _, o := range os { + for i, rel := range organizations { + if !(o.E164 == E164Slice[i]) { + continue + } + + rel.R.Phones = append(rel.R.Phones, o) + + o.R.Organizations = append(o.R.Organizations, rel) + } + } + + return nil +} + // commsPhoneC is where relationship counts are stored. type commsPhoneC struct { DestinationTextJobs *int64 DestinationTextLogs *int64 SourceTextLogs *int64 + Organizations *int64 } // PreloadCount sets a count in the C struct by name @@ -967,6 +1194,8 @@ func (o *CommsPhone) PreloadCount(name string, count int64) error { o.C.DestinationTextLogs = &count case "SourceTextLogs": o.C.SourceTextLogs = &count + case "Organizations": + o.C.Organizations = &count } return nil } @@ -975,6 +1204,7 @@ type commsPhoneCountPreloader struct { DestinationTextJobs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Organizations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { @@ -1030,6 +1260,26 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { return psql.Group(psql.Select(subqueryMods...).Expression) }) }, + Organizations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("Organizations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsPhones.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(DistrictSubscriptionPhones.Name()), + sm.Where(psql.Quote(DistrictSubscriptionPhones.Alias(), "phone_e164").EQ(psql.Quote(parent, "e164"))), + sm.InnerJoin(Organizations.Name()).On( + psql.Quote(Organizations.Alias(), "id").EQ(psql.Quote(DistrictSubscriptionPhones.Alias(), "organization_id")), + ), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, } } @@ -1037,6 +1287,7 @@ type commsPhoneCountThenLoader[Q orm.Loadable] struct { DestinationTextJobs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organizations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[Q] { @@ -1049,6 +1300,9 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ type SourceTextLogsCountInterface interface { LoadCountSourceTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type OrganizationsCountInterface interface { + LoadCountOrganizations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return commsPhoneCountThenLoader[Q]{ DestinationTextJobs: countThenLoadBuilder[Q]( @@ -1069,6 +1323,12 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ return retrieved.LoadCountSourceTextLogs(ctx, exec, mods...) }, ), + Organizations: countThenLoadBuilder[Q]( + "Organizations", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountOrganizations(ctx, exec, mods...) + }, + ), } } @@ -1162,11 +1422,42 @@ func (os CommsPhoneSlice) LoadCountSourceTextLogs(ctx context.Context, exec bob. return nil } +// LoadCountOrganizations loads the count of Organizations into the C struct +func (o *CommsPhone) LoadCountOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Organizations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Organizations = &count + return nil +} + +// LoadCountOrganizations loads the count of Organizations for a slice +func (os CommsPhoneSlice) LoadCountOrganizations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountOrganizations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type commsPhoneJoins[Q dialect.Joinable] struct { typ string DestinationTextJobs modAs[Q, commsTextJobColumns] DestinationTextLogs modAs[Q, commsTextLogColumns] SourceTextLogs modAs[Q, commsTextLogColumns] + Organizations modAs[Q, organizationColumns] } func (j commsPhoneJoins[Q]) aliasedAs(alias string) commsPhoneJoins[Q] { @@ -1215,6 +1506,28 @@ func buildCommsPhoneJoins[Q dialect.Joinable](cols commsPhoneColumns, typ string )) } + return mods + }, + }, + Organizations: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) + + { + to := DistrictSubscriptionPhones.Columns.AliasedAs(DistrictSubscriptionPhones.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, DistrictSubscriptionPhones.Name().As(to.Alias())).On( + to.PhoneE164.EQ(cols.E164), + )) + } + { + cols := DistrictSubscriptionPhones.Columns.AliasedAs(DistrictSubscriptionPhones.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + return mods }, }, diff --git a/db/models/district_subscription_email.bob.go b/db/models/district_subscription_email.bob.go new file mode 100644 index 00000000..5334e520 --- /dev/null +++ b/db/models/district_subscription_email.bob.go @@ -0,0 +1,766 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "fmt" + "io" + + "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/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" + "github.com/aarondl/opt/omit" +) + +// DistrictSubscriptionEmail is an object representing the database table. +type DistrictSubscriptionEmail struct { + OrganizationID int32 `db:"organization_id,pk" ` + EmailContactAddress string `db:"email_contact_address,pk" ` + + R districtSubscriptionEmailR `db:"-" ` +} + +// DistrictSubscriptionEmailSlice is an alias for a slice of pointers to DistrictSubscriptionEmail. +// This should almost always be used instead of []*DistrictSubscriptionEmail. +type DistrictSubscriptionEmailSlice []*DistrictSubscriptionEmail + +// DistrictSubscriptionEmails contains methods to work with the district_subscription_email table +var DistrictSubscriptionEmails = psql.NewTablex[*DistrictSubscriptionEmail, DistrictSubscriptionEmailSlice, *DistrictSubscriptionEmailSetter]("", "district_subscription_email", buildDistrictSubscriptionEmailColumns("district_subscription_email")) + +// DistrictSubscriptionEmailsQuery is a query on the district_subscription_email table +type DistrictSubscriptionEmailsQuery = *psql.ViewQuery[*DistrictSubscriptionEmail, DistrictSubscriptionEmailSlice] + +// districtSubscriptionEmailR is where relationships are stored. +type districtSubscriptionEmailR struct { + EmailContactAddressEmailContact *CommsEmailContact // district_subscription_email.district_subscription_email_email_contact_address_fkey + Organization *Organization // district_subscription_email.district_subscription_email_organization_id_fkey +} + +func buildDistrictSubscriptionEmailColumns(alias string) districtSubscriptionEmailColumns { + return districtSubscriptionEmailColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "organization_id", "email_contact_address", + ).WithParent("district_subscription_email"), + tableAlias: alias, + OrganizationID: psql.Quote(alias, "organization_id"), + EmailContactAddress: psql.Quote(alias, "email_contact_address"), + } +} + +type districtSubscriptionEmailColumns struct { + expr.ColumnsExpr + tableAlias string + OrganizationID psql.Expression + EmailContactAddress psql.Expression +} + +func (c districtSubscriptionEmailColumns) Alias() string { + return c.tableAlias +} + +func (districtSubscriptionEmailColumns) AliasedAs(alias string) districtSubscriptionEmailColumns { + return buildDistrictSubscriptionEmailColumns(alias) +} + +// DistrictSubscriptionEmailSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type DistrictSubscriptionEmailSetter struct { + OrganizationID omit.Val[int32] `db:"organization_id,pk" ` + EmailContactAddress omit.Val[string] `db:"email_contact_address,pk" ` +} + +func (s DistrictSubscriptionEmailSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.OrganizationID.IsValue() { + vals = append(vals, "organization_id") + } + if s.EmailContactAddress.IsValue() { + vals = append(vals, "email_contact_address") + } + return vals +} + +func (s DistrictSubscriptionEmailSetter) Overwrite(t *DistrictSubscriptionEmail) { + if s.OrganizationID.IsValue() { + t.OrganizationID = s.OrganizationID.MustGet() + } + if s.EmailContactAddress.IsValue() { + t.EmailContactAddress = s.EmailContactAddress.MustGet() + } +} + +func (s *DistrictSubscriptionEmailSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return DistrictSubscriptionEmails.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 2) + if s.OrganizationID.IsValue() { + vals[0] = psql.Arg(s.OrganizationID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.EmailContactAddress.IsValue() { + vals[1] = psql.Arg(s.EmailContactAddress.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s DistrictSubscriptionEmailSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s DistrictSubscriptionEmailSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.OrganizationID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + + if s.EmailContactAddress.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "email_contact_address")...), + psql.Arg(s.EmailContactAddress), + }}) + } + + return exprs +} + +// FindDistrictSubscriptionEmail retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindDistrictSubscriptionEmail(ctx context.Context, exec bob.Executor, OrganizationIDPK int32, EmailContactAddressPK string, cols ...string) (*DistrictSubscriptionEmail, error) { + if len(cols) == 0 { + return DistrictSubscriptionEmails.Query( + sm.Where(DistrictSubscriptionEmails.Columns.OrganizationID.EQ(psql.Arg(OrganizationIDPK))), + sm.Where(DistrictSubscriptionEmails.Columns.EmailContactAddress.EQ(psql.Arg(EmailContactAddressPK))), + ).One(ctx, exec) + } + + return DistrictSubscriptionEmails.Query( + sm.Where(DistrictSubscriptionEmails.Columns.OrganizationID.EQ(psql.Arg(OrganizationIDPK))), + sm.Where(DistrictSubscriptionEmails.Columns.EmailContactAddress.EQ(psql.Arg(EmailContactAddressPK))), + sm.Columns(DistrictSubscriptionEmails.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// DistrictSubscriptionEmailExists checks the presence of a single record by primary key +func DistrictSubscriptionEmailExists(ctx context.Context, exec bob.Executor, OrganizationIDPK int32, EmailContactAddressPK string) (bool, error) { + return DistrictSubscriptionEmails.Query( + sm.Where(DistrictSubscriptionEmails.Columns.OrganizationID.EQ(psql.Arg(OrganizationIDPK))), + sm.Where(DistrictSubscriptionEmails.Columns.EmailContactAddress.EQ(psql.Arg(EmailContactAddressPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after DistrictSubscriptionEmail is retrieved from the database +func (o *DistrictSubscriptionEmail) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = DistrictSubscriptionEmails.AfterSelectHooks.RunHooks(ctx, exec, DistrictSubscriptionEmailSlice{o}) + case bob.QueryTypeInsert: + ctx, err = DistrictSubscriptionEmails.AfterInsertHooks.RunHooks(ctx, exec, DistrictSubscriptionEmailSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = DistrictSubscriptionEmails.AfterUpdateHooks.RunHooks(ctx, exec, DistrictSubscriptionEmailSlice{o}) + case bob.QueryTypeDelete: + ctx, err = DistrictSubscriptionEmails.AfterDeleteHooks.RunHooks(ctx, exec, DistrictSubscriptionEmailSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the DistrictSubscriptionEmail +func (o *DistrictSubscriptionEmail) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.OrganizationID, + o.EmailContactAddress, + ) +} + +func (o *DistrictSubscriptionEmail) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("district_subscription_email", "organization_id"), psql.Quote("district_subscription_email", "email_contact_address")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return o.primaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the DistrictSubscriptionEmail +func (o *DistrictSubscriptionEmail) Update(ctx context.Context, exec bob.Executor, s *DistrictSubscriptionEmailSetter) error { + v, err := DistrictSubscriptionEmails.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single DistrictSubscriptionEmail record with an executor +func (o *DistrictSubscriptionEmail) Delete(ctx context.Context, exec bob.Executor) error { + _, err := DistrictSubscriptionEmails.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the DistrictSubscriptionEmail using the executor +func (o *DistrictSubscriptionEmail) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := DistrictSubscriptionEmails.Query( + sm.Where(DistrictSubscriptionEmails.Columns.OrganizationID.EQ(psql.Arg(o.OrganizationID))), + sm.Where(DistrictSubscriptionEmails.Columns.EmailContactAddress.EQ(psql.Arg(o.EmailContactAddress))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after DistrictSubscriptionEmailSlice is retrieved from the database +func (o DistrictSubscriptionEmailSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = DistrictSubscriptionEmails.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = DistrictSubscriptionEmails.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = DistrictSubscriptionEmails.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = DistrictSubscriptionEmails.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o DistrictSubscriptionEmailSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("district_subscription_email", "organization_id"), psql.Quote("district_subscription_email", "email_contact_address")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.primaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o DistrictSubscriptionEmailSlice) copyMatchingRows(from ...*DistrictSubscriptionEmail) { + for i, old := range o { + for _, new := range from { + if new.OrganizationID != old.OrganizationID { + continue + } + if new.EmailContactAddress != old.EmailContactAddress { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o DistrictSubscriptionEmailSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return DistrictSubscriptionEmails.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *DistrictSubscriptionEmail: + o.copyMatchingRows(retrieved) + case []*DistrictSubscriptionEmail: + o.copyMatchingRows(retrieved...) + case DistrictSubscriptionEmailSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a DistrictSubscriptionEmail or a slice of DistrictSubscriptionEmail + // then run the AfterUpdateHooks on the slice + _, err = DistrictSubscriptionEmails.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o DistrictSubscriptionEmailSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return DistrictSubscriptionEmails.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *DistrictSubscriptionEmail: + o.copyMatchingRows(retrieved) + case []*DistrictSubscriptionEmail: + o.copyMatchingRows(retrieved...) + case DistrictSubscriptionEmailSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a DistrictSubscriptionEmail or a slice of DistrictSubscriptionEmail + // then run the AfterDeleteHooks on the slice + _, err = DistrictSubscriptionEmails.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o DistrictSubscriptionEmailSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals DistrictSubscriptionEmailSetter) error { + if len(o) == 0 { + return nil + } + + _, err := DistrictSubscriptionEmails.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o DistrictSubscriptionEmailSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := DistrictSubscriptionEmails.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o DistrictSubscriptionEmailSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := DistrictSubscriptionEmails.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// EmailContactAddressEmailContact starts a query for related objects on comms.email_contact +func (o *DistrictSubscriptionEmail) EmailContactAddressEmailContact(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { + return CommsEmailContacts.Query(append(mods, + sm.Where(CommsEmailContacts.Columns.Address.EQ(psql.Arg(o.EmailContactAddress))), + )...) +} + +func (os DistrictSubscriptionEmailSlice) EmailContactAddressEmailContact(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { + pkEmailContactAddress := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkEmailContactAddress = append(pkEmailContactAddress, o.EmailContactAddress) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkEmailContactAddress), "text[]")), + )) + + return CommsEmailContacts.Query(append(mods, + sm.Where(psql.Group(CommsEmailContacts.Columns.Address).OP("IN", PKArgExpr)), + )...) +} + +// Organization starts a query for related objects on organization +func (o *DistrictSubscriptionEmail) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os DistrictSubscriptionEmailSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +func attachDistrictSubscriptionEmailEmailContactAddressEmailContact0(ctx context.Context, exec bob.Executor, count int, districtSubscriptionEmail0 *DistrictSubscriptionEmail, commsEmailContact1 *CommsEmailContact) (*DistrictSubscriptionEmail, error) { + setter := &DistrictSubscriptionEmailSetter{ + EmailContactAddress: omit.From(commsEmailContact1.Address), + } + + err := districtSubscriptionEmail0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachDistrictSubscriptionEmailEmailContactAddressEmailContact0: %w", err) + } + + return districtSubscriptionEmail0, nil +} + +func (districtSubscriptionEmail0 *DistrictSubscriptionEmail) InsertEmailContactAddressEmailContact(ctx context.Context, exec bob.Executor, related *CommsEmailContactSetter) error { + var err error + + commsEmailContact1, err := CommsEmailContacts.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachDistrictSubscriptionEmailEmailContactAddressEmailContact0(ctx, exec, 1, districtSubscriptionEmail0, commsEmailContact1) + if err != nil { + return err + } + + districtSubscriptionEmail0.R.EmailContactAddressEmailContact = commsEmailContact1 + + return nil +} + +func (districtSubscriptionEmail0 *DistrictSubscriptionEmail) AttachEmailContactAddressEmailContact(ctx context.Context, exec bob.Executor, commsEmailContact1 *CommsEmailContact) error { + var err error + + _, err = attachDistrictSubscriptionEmailEmailContactAddressEmailContact0(ctx, exec, 1, districtSubscriptionEmail0, commsEmailContact1) + if err != nil { + return err + } + + districtSubscriptionEmail0.R.EmailContactAddressEmailContact = commsEmailContact1 + + return nil +} + +func attachDistrictSubscriptionEmailOrganization0(ctx context.Context, exec bob.Executor, count int, districtSubscriptionEmail0 *DistrictSubscriptionEmail, organization1 *Organization) (*DistrictSubscriptionEmail, error) { + setter := &DistrictSubscriptionEmailSetter{ + OrganizationID: omit.From(organization1.ID), + } + + err := districtSubscriptionEmail0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachDistrictSubscriptionEmailOrganization0: %w", err) + } + + return districtSubscriptionEmail0, nil +} + +func (districtSubscriptionEmail0 *DistrictSubscriptionEmail) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachDistrictSubscriptionEmailOrganization0(ctx, exec, 1, districtSubscriptionEmail0, organization1) + if err != nil { + return err + } + + districtSubscriptionEmail0.R.Organization = organization1 + + return nil +} + +func (districtSubscriptionEmail0 *DistrictSubscriptionEmail) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachDistrictSubscriptionEmailOrganization0(ctx, exec, 1, districtSubscriptionEmail0, organization1) + if err != nil { + return err + } + + districtSubscriptionEmail0.R.Organization = organization1 + + return nil +} + +type districtSubscriptionEmailWhere[Q psql.Filterable] struct { + OrganizationID psql.WhereMod[Q, int32] + EmailContactAddress psql.WhereMod[Q, string] +} + +func (districtSubscriptionEmailWhere[Q]) AliasedAs(alias string) districtSubscriptionEmailWhere[Q] { + return buildDistrictSubscriptionEmailWhere[Q](buildDistrictSubscriptionEmailColumns(alias)) +} + +func buildDistrictSubscriptionEmailWhere[Q psql.Filterable](cols districtSubscriptionEmailColumns) districtSubscriptionEmailWhere[Q] { + return districtSubscriptionEmailWhere[Q]{ + OrganizationID: psql.Where[Q, int32](cols.OrganizationID), + EmailContactAddress: psql.Where[Q, string](cols.EmailContactAddress), + } +} + +func (o *DistrictSubscriptionEmail) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "EmailContactAddressEmailContact": + rel, ok := retrieved.(*CommsEmailContact) + if !ok { + return fmt.Errorf("districtSubscriptionEmail cannot load %T as %q", retrieved, name) + } + + o.R.EmailContactAddressEmailContact = rel + + return nil + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("districtSubscriptionEmail cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + return nil + default: + return fmt.Errorf("districtSubscriptionEmail has no relationship %q", name) + } +} + +type districtSubscriptionEmailPreloader struct { + EmailContactAddressEmailContact func(...psql.PreloadOption) psql.Preloader + Organization func(...psql.PreloadOption) psql.Preloader +} + +func buildDistrictSubscriptionEmailPreloader() districtSubscriptionEmailPreloader { + return districtSubscriptionEmailPreloader{ + EmailContactAddressEmailContact: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsEmailContact, CommsEmailContactSlice](psql.PreloadRel{ + Name: "EmailContactAddressEmailContact", + Sides: []psql.PreloadSide{ + { + From: DistrictSubscriptionEmails, + To: CommsEmailContacts, + FromColumns: []string{"email_contact_address"}, + ToColumns: []string{"address"}, + }, + }, + }, CommsEmailContacts.Columns.Names(), opts...) + }, + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: DistrictSubscriptionEmails, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + } +} + +type districtSubscriptionEmailThenLoader[Q orm.Loadable] struct { + EmailContactAddressEmailContact func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildDistrictSubscriptionEmailThenLoader[Q orm.Loadable]() districtSubscriptionEmailThenLoader[Q] { + type EmailContactAddressEmailContactLoadInterface interface { + LoadEmailContactAddressEmailContact(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return districtSubscriptionEmailThenLoader[Q]{ + EmailContactAddressEmailContact: thenLoadBuilder[Q]( + "EmailContactAddressEmailContact", + func(ctx context.Context, exec bob.Executor, retrieved EmailContactAddressEmailContactLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadEmailContactAddressEmailContact(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...) + }, + ), + } +} + +// LoadEmailContactAddressEmailContact loads the districtSubscriptionEmail's EmailContactAddressEmailContact into the .R struct +func (o *DistrictSubscriptionEmail) LoadEmailContactAddressEmailContact(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.EmailContactAddressEmailContact = nil + + related, err := o.EmailContactAddressEmailContact(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.EmailContactAddressEmailContact = related + return nil +} + +// LoadEmailContactAddressEmailContact loads the districtSubscriptionEmail's EmailContactAddressEmailContact into the .R struct +func (os DistrictSubscriptionEmailSlice) LoadEmailContactAddressEmailContact(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsEmailContacts, err := os.EmailContactAddressEmailContact(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsEmailContacts { + + if !(o.EmailContactAddress == rel.Address) { + continue + } + + o.R.EmailContactAddressEmailContact = rel + break + } + } + + return nil +} + +// LoadOrganization loads the districtSubscriptionEmail's Organization into the .R struct +func (o *DistrictSubscriptionEmail) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the districtSubscriptionEmail's Organization into the .R struct +func (os DistrictSubscriptionEmailSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + + if !(o.OrganizationID == rel.ID) { + continue + } + + o.R.Organization = rel + break + } + } + + return nil +} + +type districtSubscriptionEmailJoins[Q dialect.Joinable] struct { + typ string + EmailContactAddressEmailContact modAs[Q, commsEmailContactColumns] + Organization modAs[Q, organizationColumns] +} + +func (j districtSubscriptionEmailJoins[Q]) aliasedAs(alias string) districtSubscriptionEmailJoins[Q] { + return buildDistrictSubscriptionEmailJoins[Q](buildDistrictSubscriptionEmailColumns(alias), j.typ) +} + +func buildDistrictSubscriptionEmailJoins[Q dialect.Joinable](cols districtSubscriptionEmailColumns, typ string) districtSubscriptionEmailJoins[Q] { + return districtSubscriptionEmailJoins[Q]{ + typ: typ, + EmailContactAddressEmailContact: modAs[Q, commsEmailContactColumns]{ + c: CommsEmailContacts.Columns, + f: func(to commsEmailContactColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsEmailContacts.Name().As(to.Alias())).On( + to.Address.EQ(cols.EmailContactAddress), + )) + } + + return mods + }, + }, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/district_subscription_phone.bob.go b/db/models/district_subscription_phone.bob.go new file mode 100644 index 00000000..4dd6f8a6 --- /dev/null +++ b/db/models/district_subscription_phone.bob.go @@ -0,0 +1,766 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "fmt" + "io" + + "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/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" + "github.com/aarondl/opt/omit" +) + +// DistrictSubscriptionPhone is an object representing the database table. +type DistrictSubscriptionPhone struct { + OrganizationID int32 `db:"organization_id,pk" ` + PhoneE164 string `db:"phone_e164,pk" ` + + R districtSubscriptionPhoneR `db:"-" ` +} + +// DistrictSubscriptionPhoneSlice is an alias for a slice of pointers to DistrictSubscriptionPhone. +// This should almost always be used instead of []*DistrictSubscriptionPhone. +type DistrictSubscriptionPhoneSlice []*DistrictSubscriptionPhone + +// DistrictSubscriptionPhones contains methods to work with the district_subscription_phone table +var DistrictSubscriptionPhones = psql.NewTablex[*DistrictSubscriptionPhone, DistrictSubscriptionPhoneSlice, *DistrictSubscriptionPhoneSetter]("", "district_subscription_phone", buildDistrictSubscriptionPhoneColumns("district_subscription_phone")) + +// DistrictSubscriptionPhonesQuery is a query on the district_subscription_phone table +type DistrictSubscriptionPhonesQuery = *psql.ViewQuery[*DistrictSubscriptionPhone, DistrictSubscriptionPhoneSlice] + +// districtSubscriptionPhoneR is where relationships are stored. +type districtSubscriptionPhoneR struct { + Organization *Organization // district_subscription_phone.district_subscription_phone_organization_id_fkey + PhoneE164Phone *CommsPhone // district_subscription_phone.district_subscription_phone_phone_e164_fkey +} + +func buildDistrictSubscriptionPhoneColumns(alias string) districtSubscriptionPhoneColumns { + return districtSubscriptionPhoneColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "organization_id", "phone_e164", + ).WithParent("district_subscription_phone"), + tableAlias: alias, + OrganizationID: psql.Quote(alias, "organization_id"), + PhoneE164: psql.Quote(alias, "phone_e164"), + } +} + +type districtSubscriptionPhoneColumns struct { + expr.ColumnsExpr + tableAlias string + OrganizationID psql.Expression + PhoneE164 psql.Expression +} + +func (c districtSubscriptionPhoneColumns) Alias() string { + return c.tableAlias +} + +func (districtSubscriptionPhoneColumns) AliasedAs(alias string) districtSubscriptionPhoneColumns { + return buildDistrictSubscriptionPhoneColumns(alias) +} + +// DistrictSubscriptionPhoneSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type DistrictSubscriptionPhoneSetter struct { + OrganizationID omit.Val[int32] `db:"organization_id,pk" ` + PhoneE164 omit.Val[string] `db:"phone_e164,pk" ` +} + +func (s DistrictSubscriptionPhoneSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.OrganizationID.IsValue() { + vals = append(vals, "organization_id") + } + if s.PhoneE164.IsValue() { + vals = append(vals, "phone_e164") + } + return vals +} + +func (s DistrictSubscriptionPhoneSetter) Overwrite(t *DistrictSubscriptionPhone) { + if s.OrganizationID.IsValue() { + t.OrganizationID = s.OrganizationID.MustGet() + } + if s.PhoneE164.IsValue() { + t.PhoneE164 = s.PhoneE164.MustGet() + } +} + +func (s *DistrictSubscriptionPhoneSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return DistrictSubscriptionPhones.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 2) + if s.OrganizationID.IsValue() { + vals[0] = psql.Arg(s.OrganizationID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.PhoneE164.IsValue() { + vals[1] = psql.Arg(s.PhoneE164.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s DistrictSubscriptionPhoneSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s DistrictSubscriptionPhoneSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.OrganizationID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + + if s.PhoneE164.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "phone_e164")...), + psql.Arg(s.PhoneE164), + }}) + } + + return exprs +} + +// FindDistrictSubscriptionPhone retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindDistrictSubscriptionPhone(ctx context.Context, exec bob.Executor, OrganizationIDPK int32, PhoneE164PK string, cols ...string) (*DistrictSubscriptionPhone, error) { + if len(cols) == 0 { + return DistrictSubscriptionPhones.Query( + sm.Where(DistrictSubscriptionPhones.Columns.OrganizationID.EQ(psql.Arg(OrganizationIDPK))), + sm.Where(DistrictSubscriptionPhones.Columns.PhoneE164.EQ(psql.Arg(PhoneE164PK))), + ).One(ctx, exec) + } + + return DistrictSubscriptionPhones.Query( + sm.Where(DistrictSubscriptionPhones.Columns.OrganizationID.EQ(psql.Arg(OrganizationIDPK))), + sm.Where(DistrictSubscriptionPhones.Columns.PhoneE164.EQ(psql.Arg(PhoneE164PK))), + sm.Columns(DistrictSubscriptionPhones.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// DistrictSubscriptionPhoneExists checks the presence of a single record by primary key +func DistrictSubscriptionPhoneExists(ctx context.Context, exec bob.Executor, OrganizationIDPK int32, PhoneE164PK string) (bool, error) { + return DistrictSubscriptionPhones.Query( + sm.Where(DistrictSubscriptionPhones.Columns.OrganizationID.EQ(psql.Arg(OrganizationIDPK))), + sm.Where(DistrictSubscriptionPhones.Columns.PhoneE164.EQ(psql.Arg(PhoneE164PK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after DistrictSubscriptionPhone is retrieved from the database +func (o *DistrictSubscriptionPhone) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = DistrictSubscriptionPhones.AfterSelectHooks.RunHooks(ctx, exec, DistrictSubscriptionPhoneSlice{o}) + case bob.QueryTypeInsert: + ctx, err = DistrictSubscriptionPhones.AfterInsertHooks.RunHooks(ctx, exec, DistrictSubscriptionPhoneSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = DistrictSubscriptionPhones.AfterUpdateHooks.RunHooks(ctx, exec, DistrictSubscriptionPhoneSlice{o}) + case bob.QueryTypeDelete: + ctx, err = DistrictSubscriptionPhones.AfterDeleteHooks.RunHooks(ctx, exec, DistrictSubscriptionPhoneSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the DistrictSubscriptionPhone +func (o *DistrictSubscriptionPhone) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.OrganizationID, + o.PhoneE164, + ) +} + +func (o *DistrictSubscriptionPhone) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("district_subscription_phone", "organization_id"), psql.Quote("district_subscription_phone", "phone_e164")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return o.primaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the DistrictSubscriptionPhone +func (o *DistrictSubscriptionPhone) Update(ctx context.Context, exec bob.Executor, s *DistrictSubscriptionPhoneSetter) error { + v, err := DistrictSubscriptionPhones.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single DistrictSubscriptionPhone record with an executor +func (o *DistrictSubscriptionPhone) Delete(ctx context.Context, exec bob.Executor) error { + _, err := DistrictSubscriptionPhones.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the DistrictSubscriptionPhone using the executor +func (o *DistrictSubscriptionPhone) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := DistrictSubscriptionPhones.Query( + sm.Where(DistrictSubscriptionPhones.Columns.OrganizationID.EQ(psql.Arg(o.OrganizationID))), + sm.Where(DistrictSubscriptionPhones.Columns.PhoneE164.EQ(psql.Arg(o.PhoneE164))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after DistrictSubscriptionPhoneSlice is retrieved from the database +func (o DistrictSubscriptionPhoneSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = DistrictSubscriptionPhones.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = DistrictSubscriptionPhones.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = DistrictSubscriptionPhones.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = DistrictSubscriptionPhones.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o DistrictSubscriptionPhoneSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("district_subscription_phone", "organization_id"), psql.Quote("district_subscription_phone", "phone_e164")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.primaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o DistrictSubscriptionPhoneSlice) copyMatchingRows(from ...*DistrictSubscriptionPhone) { + for i, old := range o { + for _, new := range from { + if new.OrganizationID != old.OrganizationID { + continue + } + if new.PhoneE164 != old.PhoneE164 { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o DistrictSubscriptionPhoneSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return DistrictSubscriptionPhones.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *DistrictSubscriptionPhone: + o.copyMatchingRows(retrieved) + case []*DistrictSubscriptionPhone: + o.copyMatchingRows(retrieved...) + case DistrictSubscriptionPhoneSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a DistrictSubscriptionPhone or a slice of DistrictSubscriptionPhone + // then run the AfterUpdateHooks on the slice + _, err = DistrictSubscriptionPhones.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o DistrictSubscriptionPhoneSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return DistrictSubscriptionPhones.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *DistrictSubscriptionPhone: + o.copyMatchingRows(retrieved) + case []*DistrictSubscriptionPhone: + o.copyMatchingRows(retrieved...) + case DistrictSubscriptionPhoneSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a DistrictSubscriptionPhone or a slice of DistrictSubscriptionPhone + // then run the AfterDeleteHooks on the slice + _, err = DistrictSubscriptionPhones.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o DistrictSubscriptionPhoneSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals DistrictSubscriptionPhoneSetter) error { + if len(o) == 0 { + return nil + } + + _, err := DistrictSubscriptionPhones.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o DistrictSubscriptionPhoneSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := DistrictSubscriptionPhones.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o DistrictSubscriptionPhoneSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := DistrictSubscriptionPhones.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// Organization starts a query for related objects on organization +func (o *DistrictSubscriptionPhone) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os DistrictSubscriptionPhoneSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +// PhoneE164Phone starts a query for related objects on comms.phone +func (o *DistrictSubscriptionPhone) PhoneE164Phone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + return CommsPhones.Query(append(mods, + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.PhoneE164))), + )...) +} + +func (os DistrictSubscriptionPhoneSlice) PhoneE164Phone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + pkPhoneE164 := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkPhoneE164 = append(pkPhoneE164, o.PhoneE164) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkPhoneE164), "text[]")), + )) + + return CommsPhones.Query(append(mods, + sm.Where(psql.Group(CommsPhones.Columns.E164).OP("IN", PKArgExpr)), + )...) +} + +func attachDistrictSubscriptionPhoneOrganization0(ctx context.Context, exec bob.Executor, count int, districtSubscriptionPhone0 *DistrictSubscriptionPhone, organization1 *Organization) (*DistrictSubscriptionPhone, error) { + setter := &DistrictSubscriptionPhoneSetter{ + OrganizationID: omit.From(organization1.ID), + } + + err := districtSubscriptionPhone0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachDistrictSubscriptionPhoneOrganization0: %w", err) + } + + return districtSubscriptionPhone0, nil +} + +func (districtSubscriptionPhone0 *DistrictSubscriptionPhone) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachDistrictSubscriptionPhoneOrganization0(ctx, exec, 1, districtSubscriptionPhone0, organization1) + if err != nil { + return err + } + + districtSubscriptionPhone0.R.Organization = organization1 + + return nil +} + +func (districtSubscriptionPhone0 *DistrictSubscriptionPhone) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachDistrictSubscriptionPhoneOrganization0(ctx, exec, 1, districtSubscriptionPhone0, organization1) + if err != nil { + return err + } + + districtSubscriptionPhone0.R.Organization = organization1 + + return nil +} + +func attachDistrictSubscriptionPhonePhoneE164Phone0(ctx context.Context, exec bob.Executor, count int, districtSubscriptionPhone0 *DistrictSubscriptionPhone, commsPhone1 *CommsPhone) (*DistrictSubscriptionPhone, error) { + setter := &DistrictSubscriptionPhoneSetter{ + PhoneE164: omit.From(commsPhone1.E164), + } + + err := districtSubscriptionPhone0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachDistrictSubscriptionPhonePhoneE164Phone0: %w", err) + } + + return districtSubscriptionPhone0, nil +} + +func (districtSubscriptionPhone0 *DistrictSubscriptionPhone) InsertPhoneE164Phone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { + var err error + + commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachDistrictSubscriptionPhonePhoneE164Phone0(ctx, exec, 1, districtSubscriptionPhone0, commsPhone1) + if err != nil { + return err + } + + districtSubscriptionPhone0.R.PhoneE164Phone = commsPhone1 + + return nil +} + +func (districtSubscriptionPhone0 *DistrictSubscriptionPhone) AttachPhoneE164Phone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { + var err error + + _, err = attachDistrictSubscriptionPhonePhoneE164Phone0(ctx, exec, 1, districtSubscriptionPhone0, commsPhone1) + if err != nil { + return err + } + + districtSubscriptionPhone0.R.PhoneE164Phone = commsPhone1 + + return nil +} + +type districtSubscriptionPhoneWhere[Q psql.Filterable] struct { + OrganizationID psql.WhereMod[Q, int32] + PhoneE164 psql.WhereMod[Q, string] +} + +func (districtSubscriptionPhoneWhere[Q]) AliasedAs(alias string) districtSubscriptionPhoneWhere[Q] { + return buildDistrictSubscriptionPhoneWhere[Q](buildDistrictSubscriptionPhoneColumns(alias)) +} + +func buildDistrictSubscriptionPhoneWhere[Q psql.Filterable](cols districtSubscriptionPhoneColumns) districtSubscriptionPhoneWhere[Q] { + return districtSubscriptionPhoneWhere[Q]{ + OrganizationID: psql.Where[Q, int32](cols.OrganizationID), + PhoneE164: psql.Where[Q, string](cols.PhoneE164), + } +} + +func (o *DistrictSubscriptionPhone) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("districtSubscriptionPhone cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + return nil + case "PhoneE164Phone": + rel, ok := retrieved.(*CommsPhone) + if !ok { + return fmt.Errorf("districtSubscriptionPhone cannot load %T as %q", retrieved, name) + } + + o.R.PhoneE164Phone = rel + + return nil + default: + return fmt.Errorf("districtSubscriptionPhone has no relationship %q", name) + } +} + +type districtSubscriptionPhonePreloader struct { + Organization func(...psql.PreloadOption) psql.Preloader + PhoneE164Phone func(...psql.PreloadOption) psql.Preloader +} + +func buildDistrictSubscriptionPhonePreloader() districtSubscriptionPhonePreloader { + return districtSubscriptionPhonePreloader{ + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: DistrictSubscriptionPhones, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + PhoneE164Phone: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ + Name: "PhoneE164Phone", + Sides: []psql.PreloadSide{ + { + From: DistrictSubscriptionPhones, + To: CommsPhones, + FromColumns: []string{"phone_e164"}, + ToColumns: []string{"e164"}, + }, + }, + }, CommsPhones.Columns.Names(), opts...) + }, + } +} + +type districtSubscriptionPhoneThenLoader[Q orm.Loadable] struct { + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + PhoneE164Phone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildDistrictSubscriptionPhoneThenLoader[Q orm.Loadable]() districtSubscriptionPhoneThenLoader[Q] { + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PhoneE164PhoneLoadInterface interface { + LoadPhoneE164Phone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return districtSubscriptionPhoneThenLoader[Q]{ + 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...) + }, + ), + PhoneE164Phone: thenLoadBuilder[Q]( + "PhoneE164Phone", + func(ctx context.Context, exec bob.Executor, retrieved PhoneE164PhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadPhoneE164Phone(ctx, exec, mods...) + }, + ), + } +} + +// LoadOrganization loads the districtSubscriptionPhone's Organization into the .R struct +func (o *DistrictSubscriptionPhone) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the districtSubscriptionPhone's Organization into the .R struct +func (os DistrictSubscriptionPhoneSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + + if !(o.OrganizationID == rel.ID) { + continue + } + + o.R.Organization = rel + break + } + } + + return nil +} + +// LoadPhoneE164Phone loads the districtSubscriptionPhone's PhoneE164Phone into the .R struct +func (o *DistrictSubscriptionPhone) LoadPhoneE164Phone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.PhoneE164Phone = nil + + related, err := o.PhoneE164Phone(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.PhoneE164Phone = related + return nil +} + +// LoadPhoneE164Phone loads the districtSubscriptionPhone's PhoneE164Phone into the .R struct +func (os DistrictSubscriptionPhoneSlice) LoadPhoneE164Phone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsPhones, err := os.PhoneE164Phone(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsPhones { + + if !(o.PhoneE164 == rel.E164) { + continue + } + + o.R.PhoneE164Phone = rel + break + } + } + + return nil +} + +type districtSubscriptionPhoneJoins[Q dialect.Joinable] struct { + typ string + Organization modAs[Q, organizationColumns] + PhoneE164Phone modAs[Q, commsPhoneColumns] +} + +func (j districtSubscriptionPhoneJoins[Q]) aliasedAs(alias string) districtSubscriptionPhoneJoins[Q] { + return buildDistrictSubscriptionPhoneJoins[Q](buildDistrictSubscriptionPhoneColumns(alias), j.typ) +} + +func buildDistrictSubscriptionPhoneJoins[Q dialect.Joinable](cols districtSubscriptionPhoneColumns, typ string) districtSubscriptionPhoneJoins[Q] { + return districtSubscriptionPhoneJoins[Q]{ + typ: typ, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + + return mods + }, + }, + PhoneE164Phone: modAs[Q, commsPhoneColumns]{ + c: CommsPhones.Columns, + f: func(to commsPhoneColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( + to.E164.EQ(cols.PhoneE164), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index 40ce86eb..5a2d23de 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "strconv" "github.com/Gleipnir-Technology/bob" "github.com/Gleipnir-Technology/bob/dialect/psql" @@ -22,6 +23,7 @@ import ( "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" + "github.com/stephenafamo/scan" ) // Organization is an object representing the database table. @@ -53,6 +55,8 @@ type OrganizationsQuery = *psql.ViewQuery[*Organization, OrganizationSlice] // organizationR is where relationships are stored. type organizationR struct { + EmailContacts CommsEmailContactSlice // district_subscription_email.district_subscription_email_email_contact_address_fkeydistrict_subscription_email.district_subscription_email_organization_id_fkey + Phones CommsPhoneSlice // district_subscription_phone.district_subscription_phone_organization_id_fkeydistrict_subscription_phone.district_subscription_phone_phone_e164_fkey Containerrelates FieldseekerContainerrelateSlice // fieldseeker.containerrelate.containerrelate_organization_id_fkey Fieldscoutinglogs FieldseekerFieldscoutinglogSlice // fieldseeker.fieldscoutinglog.fieldscoutinglog_organization_id_fkey Habitatrelates FieldseekerHabitatrelateSlice // fieldseeker.habitatrelate.habitatrelate_organization_id_fkey @@ -569,6 +573,64 @@ func (o OrganizationSlice) ReloadAll(ctx context.Context, exec bob.Executor) err return nil } +// EmailContacts starts a query for related objects on comms.email_contact +func (o *Organization) EmailContacts(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { + return CommsEmailContacts.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionEmails.NameAs()).On( + CommsEmailContacts.Columns.Address.EQ(DistrictSubscriptionEmails.Columns.EmailContactAddress)), + sm.Where(DistrictSubscriptionEmails.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) EmailContacts(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { + 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 CommsEmailContacts.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionEmails.NameAs()).On( + CommsEmailContacts.Columns.Address.EQ(DistrictSubscriptionEmails.Columns.EmailContactAddress), + ), + sm.Where(psql.Group(DistrictSubscriptionEmails.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + +// Phones starts a query for related objects on comms.phone +func (o *Organization) Phones(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + return CommsPhones.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionPhones.NameAs()).On( + CommsPhones.Columns.E164.EQ(DistrictSubscriptionPhones.Columns.PhoneE164)), + sm.Where(DistrictSubscriptionPhones.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) Phones(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + 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 CommsPhones.Query(append(mods, + sm.InnerJoin(DistrictSubscriptionPhones.NameAs()).On( + CommsPhones.Columns.E164.EQ(DistrictSubscriptionPhones.Columns.PhoneE164), + ), + sm.Where(psql.Group(DistrictSubscriptionPhones.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + // Containerrelates starts a query for related objects on fieldseeker.containerrelate func (o *Organization) Containerrelates(mods ...bob.Mod[*dialect.SelectQuery]) FieldseekerContainerrelatesQuery { return FieldseekerContainerrelates.Query(append(mods, @@ -1433,6 +1495,136 @@ func (os OrganizationSlice) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQue )...) } +func attachOrganizationEmailContacts0(ctx context.Context, exec bob.Executor, count int, organization0 *Organization, commsEmailContacts2 CommsEmailContactSlice) (DistrictSubscriptionEmailSlice, error) { + setters := make([]*DistrictSubscriptionEmailSetter, count) + for i := range count { + setters[i] = &DistrictSubscriptionEmailSetter{ + OrganizationID: omit.From(organization0.ID), + EmailContactAddress: omit.From(commsEmailContacts2[i].Address), + } + } + + districtSubscriptionEmails1, err := DistrictSubscriptionEmails.Insert(bob.ToMods(setters...)).All(ctx, exec) + if err != nil { + return nil, fmt.Errorf("attachOrganizationEmailContacts0: %w", err) + } + + return districtSubscriptionEmails1, nil +} + +func (organization0 *Organization) InsertEmailContacts(ctx context.Context, exec bob.Executor, related ...*CommsEmailContactSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + inserted, err := CommsEmailContacts.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + commsEmailContacts2 := CommsEmailContactSlice(inserted) + + _, err = attachOrganizationEmailContacts0(ctx, exec, len(related), organization0, commsEmailContacts2) + if err != nil { + return err + } + + organization0.R.EmailContacts = append(organization0.R.EmailContacts, commsEmailContacts2...) + + for _, rel := range commsEmailContacts2 { + rel.R.Organizations = append(rel.R.Organizations, organization0) + } + return nil +} + +func (organization0 *Organization) AttachEmailContacts(ctx context.Context, exec bob.Executor, related ...*CommsEmailContact) error { + if len(related) == 0 { + return nil + } + + var err error + commsEmailContacts2 := CommsEmailContactSlice(related) + + _, err = attachOrganizationEmailContacts0(ctx, exec, len(related), organization0, commsEmailContacts2) + if err != nil { + return err + } + + organization0.R.EmailContacts = append(organization0.R.EmailContacts, commsEmailContacts2...) + + for _, rel := range related { + rel.R.Organizations = append(rel.R.Organizations, organization0) + } + + return nil +} + +func attachOrganizationPhones0(ctx context.Context, exec bob.Executor, count int, organization0 *Organization, commsPhones2 CommsPhoneSlice) (DistrictSubscriptionPhoneSlice, error) { + setters := make([]*DistrictSubscriptionPhoneSetter, count) + for i := range count { + setters[i] = &DistrictSubscriptionPhoneSetter{ + OrganizationID: omit.From(organization0.ID), + PhoneE164: omit.From(commsPhones2[i].E164), + } + } + + districtSubscriptionPhones1, err := DistrictSubscriptionPhones.Insert(bob.ToMods(setters...)).All(ctx, exec) + if err != nil { + return nil, fmt.Errorf("attachOrganizationPhones0: %w", err) + } + + return districtSubscriptionPhones1, nil +} + +func (organization0 *Organization) InsertPhones(ctx context.Context, exec bob.Executor, related ...*CommsPhoneSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + inserted, err := CommsPhones.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + commsPhones2 := CommsPhoneSlice(inserted) + + _, err = attachOrganizationPhones0(ctx, exec, len(related), organization0, commsPhones2) + if err != nil { + return err + } + + organization0.R.Phones = append(organization0.R.Phones, commsPhones2...) + + for _, rel := range commsPhones2 { + rel.R.Organizations = append(rel.R.Organizations, organization0) + } + return nil +} + +func (organization0 *Organization) AttachPhones(ctx context.Context, exec bob.Executor, related ...*CommsPhone) error { + if len(related) == 0 { + return nil + } + + var err error + commsPhones2 := CommsPhoneSlice(related) + + _, err = attachOrganizationPhones0(ctx, exec, len(related), organization0, commsPhones2) + if err != nil { + return err + } + + organization0.R.Phones = append(organization0.R.Phones, commsPhones2...) + + for _, rel := range related { + rel.R.Organizations = append(rel.R.Organizations, organization0) + } + + return nil +} + func insertOrganizationContainerrelates0(ctx context.Context, exec bob.Executor, fieldseekerContainerrelates1 []*FieldseekerContainerrelateSetter, organization0 *Organization) (FieldseekerContainerrelateSlice, error) { for i := range fieldseekerContainerrelates1 { fieldseekerContainerrelates1[i].OrganizationID = omit.From(organization0.ID) @@ -3897,6 +4089,34 @@ func (o *Organization) Preload(name string, retrieved any) error { } switch name { + case "EmailContacts": + rels, ok := retrieved.(CommsEmailContactSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.EmailContacts = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organizations = OrganizationSlice{o} + } + } + return nil + case "Phones": + rels, ok := retrieved.(CommsPhoneSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.Phones = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organizations = OrganizationSlice{o} + } + } + return nil case "Containerrelates": rels, ok := retrieved.(FieldseekerContainerrelateSlice) if !ok { @@ -4427,6 +4647,8 @@ func buildOrganizationPreloader() organizationPreloader { } type organizationThenLoader[Q orm.Loadable] struct { + EmailContacts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Phones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Containerrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] @@ -4466,6 +4688,12 @@ type organizationThenLoader[Q orm.Loadable] struct { } func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { + type EmailContactsLoadInterface interface { + LoadEmailContacts(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PhonesLoadInterface interface { + LoadPhones(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type ContainerrelatesLoadInterface interface { LoadContainerrelates(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -4576,6 +4804,18 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { } return organizationThenLoader[Q]{ + EmailContacts: thenLoadBuilder[Q]( + "EmailContacts", + func(ctx context.Context, exec bob.Executor, retrieved EmailContactsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadEmailContacts(ctx, exec, mods...) + }, + ), + Phones: thenLoadBuilder[Q]( + "Phones", + func(ctx context.Context, exec bob.Executor, retrieved PhonesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadPhones(ctx, exec, mods...) + }, + ), Containerrelates: thenLoadBuilder[Q]( "Containerrelates", func(ctx context.Context, exec bob.Executor, retrieved ContainerrelatesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -4795,6 +5035,168 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { } } +// LoadEmailContacts loads the organization's EmailContacts into the .R struct +func (o *Organization) LoadEmailContacts(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.EmailContacts = nil + + related, err := o.EmailContacts(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organizations = OrganizationSlice{o} + } + + o.R.EmailContacts = related + return nil +} + +// LoadEmailContacts loads the organization's EmailContacts into the .R struct +func (os OrganizationSlice) LoadEmailContacts(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(CommsEmailContacts.Columns)) + } + + q := os.EmailContacts(append( + mods, + sm.Columns(DistrictSubscriptionEmails.Columns.OrganizationID.As("related_organization.ID")), + )...) + + IDSlice := []int32{} + + mapper := scan.Mod(scan.StructMapper[*CommsEmailContact](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + IDSlice = append(IDSlice, *new(int32)) + row.ScheduleScanByName("related_organization.ID", &IDSlice[len(IDSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + commsEmailContacts, err := bob.Allx[bob.SliceTransformer[*CommsEmailContact, CommsEmailContactSlice]](ctx, exec, q, mapper) + if err != nil { + return err + } + + for _, o := range os { + o.R.EmailContacts = nil + } + + for _, o := range os { + for i, rel := range commsEmailContacts { + if !(o.ID == IDSlice[i]) { + continue + } + + rel.R.Organizations = append(rel.R.Organizations, o) + + o.R.EmailContacts = append(o.R.EmailContacts, rel) + } + } + + return nil +} + +// LoadPhones loads the organization's Phones into the .R struct +func (o *Organization) LoadPhones(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Phones = nil + + related, err := o.Phones(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organizations = OrganizationSlice{o} + } + + o.R.Phones = related + return nil +} + +// LoadPhones loads the organization's Phones into the .R struct +func (os OrganizationSlice) LoadPhones(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(CommsPhones.Columns)) + } + + q := os.Phones(append( + mods, + sm.Columns(DistrictSubscriptionPhones.Columns.OrganizationID.As("related_organization.ID")), + )...) + + IDSlice := []int32{} + + mapper := scan.Mod(scan.StructMapper[*CommsPhone](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + IDSlice = append(IDSlice, *new(int32)) + row.ScheduleScanByName("related_organization.ID", &IDSlice[len(IDSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + commsPhones, err := bob.Allx[bob.SliceTransformer[*CommsPhone, CommsPhoneSlice]](ctx, exec, q, mapper) + if err != nil { + return err + } + + for _, o := range os { + o.R.Phones = nil + } + + for _, o := range os { + for i, rel := range commsPhones { + if !(o.ID == IDSlice[i]) { + continue + } + + rel.R.Organizations = append(rel.R.Organizations, o) + + o.R.Phones = append(o.R.Phones, rel) + } + } + + return nil +} + // LoadContainerrelates loads the organization's Containerrelates into the .R struct func (o *Organization) LoadContainerrelates(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -6996,6 +7398,8 @@ func (os OrganizationSlice) LoadUser(ctx context.Context, exec bob.Executor, mod // organizationC is where relationship counts are stored. type organizationC struct { + EmailContacts *int64 + Phones *int64 Containerrelates *int64 Fieldscoutinglogs *int64 Habitatrelates *int64 @@ -7040,6 +7444,10 @@ func (o *Organization) PreloadCount(name string, count int64) error { } switch name { + case "EmailContacts": + o.C.EmailContacts = &count + case "Phones": + o.C.Phones = &count case "Containerrelates": o.C.Containerrelates = &count case "Fieldscoutinglogs": @@ -7115,6 +7523,8 @@ func (o *Organization) PreloadCount(name string, count int64) error { } type organizationCountPreloader struct { + EmailContacts func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Phones func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader Containerrelates func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader @@ -7154,6 +7564,46 @@ type organizationCountPreloader struct { func buildOrganizationCountPreloader() organizationCountPreloader { return organizationCountPreloader{ + EmailContacts: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("EmailContacts", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(DistrictSubscriptionEmails.Name()), + sm.Where(psql.Quote(DistrictSubscriptionEmails.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + sm.InnerJoin(CommsEmailContacts.Name()).On( + psql.Quote(CommsEmailContacts.Alias(), "address").EQ(psql.Quote(DistrictSubscriptionEmails.Alias(), "email_contact_address")), + ), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Phones: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Phones", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(DistrictSubscriptionPhones.Name()), + sm.Where(psql.Quote(DistrictSubscriptionPhones.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + sm.InnerJoin(CommsPhones.Name()).On( + psql.Quote(CommsPhones.Alias(), "e164").EQ(psql.Quote(DistrictSubscriptionPhones.Alias(), "phone_e164")), + ), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, Containerrelates: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { return countPreloader[*Organization]("Containerrelates", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) @@ -7753,6 +8203,8 @@ func buildOrganizationCountPreloader() organizationCountPreloader { } type organizationCountThenLoader[Q orm.Loadable] struct { + EmailContacts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Phones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Containerrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] @@ -7791,6 +8243,12 @@ type organizationCountThenLoader[Q orm.Loadable] struct { } func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoader[Q] { + type EmailContactsCountInterface interface { + LoadCountEmailContacts(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PhonesCountInterface interface { + LoadCountPhones(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type ContainerrelatesCountInterface interface { LoadCountContainerrelates(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -7898,6 +8356,18 @@ func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoa } return organizationCountThenLoader[Q]{ + EmailContacts: countThenLoadBuilder[Q]( + "EmailContacts", + func(ctx context.Context, exec bob.Executor, retrieved EmailContactsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountEmailContacts(ctx, exec, mods...) + }, + ), + Phones: countThenLoadBuilder[Q]( + "Phones", + func(ctx context.Context, exec bob.Executor, retrieved PhonesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPhones(ctx, exec, mods...) + }, + ), Containerrelates: countThenLoadBuilder[Q]( "Containerrelates", func(ctx context.Context, exec bob.Executor, retrieved ContainerrelatesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -8111,6 +8581,66 @@ func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoa } } +// LoadCountEmailContacts loads the count of EmailContacts into the C struct +func (o *Organization) LoadCountEmailContacts(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.EmailContacts(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.EmailContacts = &count + return nil +} + +// LoadCountEmailContacts loads the count of EmailContacts for a slice +func (os OrganizationSlice) LoadCountEmailContacts(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountEmailContacts(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPhones loads the count of Phones into the C struct +func (o *Organization) LoadCountPhones(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Phones(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Phones = &count + return nil +} + +// LoadCountPhones loads the count of Phones for a slice +func (os OrganizationSlice) LoadCountPhones(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPhones(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + // LoadCountContainerrelates loads the count of Containerrelates into the C struct func (o *Organization) LoadCountContainerrelates(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -9163,6 +9693,8 @@ func (os OrganizationSlice) LoadCountUser(ctx context.Context, exec bob.Executor type organizationJoins[Q dialect.Joinable] struct { typ string + EmailContacts modAs[Q, commsEmailContactColumns] + Phones modAs[Q, commsPhoneColumns] Containerrelates modAs[Q, fieldseekerContainerrelateColumns] Fieldscoutinglogs modAs[Q, fieldseekerFieldscoutinglogColumns] Habitatrelates modAs[Q, fieldseekerHabitatrelateColumns] @@ -9208,6 +9740,50 @@ func (j organizationJoins[Q]) aliasedAs(alias string) organizationJoins[Q] { func buildOrganizationJoins[Q dialect.Joinable](cols organizationColumns, typ string) organizationJoins[Q] { return organizationJoins[Q]{ typ: typ, + EmailContacts: modAs[Q, commsEmailContactColumns]{ + c: CommsEmailContacts.Columns, + f: func(to commsEmailContactColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) + + { + to := DistrictSubscriptionEmails.Columns.AliasedAs(DistrictSubscriptionEmails.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, DistrictSubscriptionEmails.Name().As(to.Alias())).On( + to.OrganizationID.EQ(cols.ID), + )) + } + { + cols := DistrictSubscriptionEmails.Columns.AliasedAs(DistrictSubscriptionEmails.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, CommsEmailContacts.Name().As(to.Alias())).On( + to.Address.EQ(cols.EmailContactAddress), + )) + } + + return mods + }, + }, + Phones: modAs[Q, commsPhoneColumns]{ + c: CommsPhones.Columns, + f: func(to commsPhoneColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) + + { + to := DistrictSubscriptionPhones.Columns.AliasedAs(DistrictSubscriptionPhones.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, DistrictSubscriptionPhones.Name().As(to.Alias())).On( + to.OrganizationID.EQ(cols.ID), + )) + } + { + cols := DistrictSubscriptionPhones.Columns.AliasedAs(DistrictSubscriptionPhones.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( + to.E164.EQ(cols.PhoneE164), + )) + } + + return mods + }, + }, Containerrelates: modAs[Q, fieldseekerContainerrelateColumns]{ c: FieldseekerContainerrelates.Columns, f: func(to fieldseekerContainerrelateColumns) bob.Mod[Q] { diff --git a/platform/report/notification.go b/platform/report/notification.go new file mode 100644 index 00000000..392c9549 --- /dev/null +++ b/platform/report/notification.go @@ -0,0 +1,128 @@ +package report + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "strings" + + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/nidus-sync/background" + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/db/sql" + "github.com/nyaruka/phonenumbers" + "github.com/rs/zerolog/log" +) + +type E164 = phonenumbers.PhoneNumber + +type ErrorWithCode struct { + code string + err error + message string +} + +func (e *ErrorWithCode) Code() string { + return e.code +} +func (e *ErrorWithCode) Error() string { + return e.message +} + +type SomeReport struct { + report_id string + type_ string +} + +// GenerateReportID creates a 12-character random string using only unambiguous +// capital letters and numbers +func GenerateReportID() (string, error) { + // Define character set (no O/0, I/l/1, 2/Z to avoid confusion) + const charset = "ABCDEFGHJKLMNPQRSTUVWXY3456789" + const length = 12 + + var builder strings.Builder + builder.Grow(length) + + // Use crypto/rand for secure randomness + for i := 0; i < length; i++ { + // Generate a random index within our charset + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + if err != nil { + return "", fmt.Errorf("failed to generate random number: %w", err) + } + + // Add the randomly selected character to our ID + builder.WriteByte(charset[n.Int64()]) + } + + return builder.String(), nil +} + +func RegisterNotifications(ctx context.Context, report_id string, email string, phone *E164) *ErrorWithCode { + result, err := psql.Update( + um.Table("publicreport.quick"), + um.SetCol("reporter_email").ToArg(email), + um.SetCol("reporter_phone").ToArg(phone), + um.Where(psql.Quote("public_id").EQ(psql.Arg(report_id))), + ).Exec(ctx, db.PGInstance.BobDB) + if err != nil { + return newErrorWithCode("internal-error", "Failed to update report: %w", err) + } + rowcount, err := result.RowsAffected() + if err != nil { + return newErrorWithCode("internal-error", "Failed to get rows affected: %w", err) + } + if rowcount != 1 { + log.Warn().Str("report_id", report_id).Msg("updated more than one row, which is a programmer error") + } + if email != "" { + background.ReportSubscriptionConfirmationEmail(email, report_id) + } + if phone != nil { + background.ReportSubscriptionConfirmationText(*phone, report_id) + } + return nil +} + +func RegisterSubscriptionEmail(ctx context.Context, email string) *ErrorWithCode { + log.Warn().Msg("RegisterSubscription not implemented yet") + return nil +} +func RegisterSubscriptionPhone(ctx context.Context, phone *E164) *ErrorWithCode { + log.Warn().Msg("RegisterSubscription not implemented yet") + return nil +} + +func findSomeReport(ctx context.Context, report_id string) (result SomeReport, err *ErrorWithCode) { + rows, e := sql.PublicreportIDTable(report_id).All(ctx, db.PGInstance.BobDB) + if e != nil { + return result, newErrorWithCode("internal-error", "Failed to query report ID table: %w", e) + } + switch len(rows) { + case 0: + return result, newErrorWithCode("invalid-report-id", "No reports match the provided ID") + case 1: + break + default: + return result, newErrorWithCode("internal-error", "More than one report with the provided ID, which shouldn't happen") + } + return result, nil +} + +func newErrorWithCode(code string, format string, args ...any) *ErrorWithCode { + if len(args) > 0 { + return &ErrorWithCode{ + err: fmt.Errorf(format, args...), + code: code, + } + } else { + return &ErrorWithCode{ + code: code, + err: nil, + message: format, + } + } +} diff --git a/platform/text/report-subscription.go b/platform/text/report-subscription.go index b7aefce1..921f72b1 100644 --- a/platform/text/report-subscription.go +++ b/platform/text/report-subscription.go @@ -52,11 +52,12 @@ func sendReportSubscription(ctx context.Context, job Job) error { if err != nil { return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) } - sub, err := isSubscribed(ctx, job.destination()) + status, err := phoneStatus(ctx, job.destination()) if err != nil { return fmt.Errorf("Failed to check if subscribed: %w", err) } - if sub == nil { + switch status { + case enums.CommsPhonestatustypeUnconfirmed: err = delayMessage(ctx, enums.CommsTextjobsourceRmo, j.destination(), j.content(), enums.CommsTextjobtypeReportConfirmation) if err != nil { return fmt.Errorf("Failed to delay report subscription message: %w", err) @@ -66,13 +67,12 @@ func sendReportSubscription(ctx context.Context, job Job) error { return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) } return nil - } - if *sub { + case enums.CommsPhonestatustypeOkToSend: err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction, false, true) if err != nil { return fmt.Errorf("Failed to send report subscription confirmation: %w", err) } - } else { + case enums.CommsPhonestatustypeStopped: resendInitialText(ctx, j.source(), j.destination()) } return nil diff --git a/platform/text/text.go b/platform/text/text.go index 949412e4..0e0741eb 100644 --- a/platform/text/text.go +++ b/platform/text/text.go @@ -31,17 +31,17 @@ func HandleTextMessage(src string, dst string, body string) { log.Error().Err(err).Str("dst", dst).Msg("Failed to add text message log") return } - subscribed, err := isSubscribed(ctx, src) + status, err := phoneStatus(ctx, src) if err != nil { - log.Error().Err(err).Msg("Failed to handle message") + log.Error().Err(err).Msg("Failed to get phone status") return } body_l := strings.TrimSpace(strings.ToLower(body)) // We don't know if they're subscribed or not. - if subscribed == nil { + if status == enums.CommsPhonestatustypeUnconfirmed { switch body_l { case "yes": - setSubscribed(ctx, src, true) + setPhoneStatus(ctx, src, enums.CommsPhonestatustypeOkToSend) handleWaitingTextJobs(ctx, src) default: content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" @@ -59,7 +59,7 @@ func HandleTextMessage(src string, dst string, body string) { if err != nil { log.Error().Err(err).Msg("Failed to send unsubscribe acknowledgement.") } - setSubscribed(ctx, src, false) + setPhoneStatus(ctx, src, enums.CommsPhonestatustypeStopped) return case "reset conversation": handleResetConversation(ctx, src, dst) @@ -145,7 +145,7 @@ func resendInitialText(ctx context.Context, src string, dst string) error { return fmt.Errorf("Failed to find phone %s: %w", dst, err) } err = phone.Update(ctx, db.PGInstance.BobDB, &models.CommsPhoneSetter{ - IsSubscribed: omitnull.FromPtr[bool](nil), + Status: omit.From(enums.CommsPhonestatustypeUnconfirmed), }) if err != nil { return fmt.Errorf("Failed to clear subscription on phone %s: %w", dst, err) @@ -185,7 +185,8 @@ func ensureInDB(ctx context.Context, destination string) (err error) { if err.Error() == "sql: no rows in result set" { _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ E164: omit.From(destination), - IsSubscribed: omitnull.FromPtr[bool](nil), + IsSubscribed: omit.From(false), + Status: omit.From(enums.CommsPhonestatustypeUnconfirmed), }).One(ctx, db.PGInstance.BobDB) if err != nil { return fmt.Errorf("Failed to insert new phone contact: %w", err) @@ -282,16 +283,12 @@ func insertTextLog(ctx context.Context, content string, destination string, sour return log, err } -func isSubscribed(ctx context.Context, src string) (*bool, error) { +func phoneStatus(ctx context.Context, src string) (enums.CommsPhonestatustype, error) { phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) if err != nil { - return nil, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) + return enums.CommsPhonestatustypeUnconfirmed, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) } - if phone.IsSubscribed.IsNull() { - return nil, nil - } - result := phone.IsSubscribed.MustGet() - return &result, nil + return phone.Status, nil } func loadPreviousMessagesForLLM(ctx context.Context, dst, src string) ([]llm.Message, error) { @@ -337,15 +334,15 @@ func sendText(ctx context.Context, source string, destination string, message st return nil } -func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { +func setPhoneStatus(ctx context.Context, src string, status enums.CommsPhonestatustype) error { phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) if err != nil { return fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) } phone.Update(ctx, db.PGInstance.BobDB, &models.CommsPhoneSetter{ - IsSubscribed: omitnull.From(is_subscribed), + Status: omit.From(status), }) - log.Info().Str("src", src).Bool("is_subscribed", is_subscribed).Msg("Set number subscribed") + log.Info().Str("src", src).Str("status", string(status)).Msg("Set number subscribed") return nil } diff --git a/rmo/form.go b/rmo/form.go new file mode 100644 index 00000000..66631f81 --- /dev/null +++ b/rmo/form.go @@ -0,0 +1,26 @@ +package rmo + +import ( + "net/http" +) + +func postFormBool(r *http.Request, k string) *bool { + v := r.PostFormValue(k) + if v == "" { + return nil + } + result := false + if v == "on" { + result = true + return &result + } + return &result +} + +func postFormValueOrNone(r *http.Request, k string) string { + v := r.PostFormValue(k) + if v == "" { + return "none" + } + return v +} diff --git a/rmo/notification.go b/rmo/notification.go new file mode 100644 index 00000000..2fbe661a --- /dev/null +++ b/rmo/notification.go @@ -0,0 +1,61 @@ +package rmo + +import ( + "fmt" + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/platform/report" + "github.com/Gleipnir-Technology/nidus-sync/platform/text" +) + +var ( + registerNotificationsCompleteT = buildTemplate("register-notifications-complete", "base") +) + +func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + respondError(w, "Failed to parse form", err, http.StatusBadRequest) + return + } + consent := r.PostFormValue("consent") + email := r.PostFormValue("email") + phone_str := r.PostFormValue("phone") + report_id := r.PostFormValue("report_id") + subscribe := postFormBool(r, "subscribe") + if consent != "on" { + respondError(w, "You must consent", nil, http.StatusBadRequest) + return + } + if email == "" && phone_str == "" { + http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", report_id), http.StatusFound) + return + } + phone, err := text.ParsePhoneNumber(phone_str) + if err != nil { + http.Redirect(w, r, fmt.Sprintf("/error?code=invalid-phone&report=%s", report_id), http.StatusFound) + return + } + + ctx := r.Context() + if subscribe != nil && *subscribe { + if email != "" { + e := report.RegisterSubscriptionEmail(ctx, email) + if e != nil { + http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", report_id, e.Code()), http.StatusFound) + } + } + if phone_str != "" { + e := report.RegisterSubscriptionPhone(ctx, phone) + if e != nil { + http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", report_id, e.Code()), http.StatusFound) + } + } + } + e := report.RegisterNotifications(ctx, report_id, email, phone) + if e != nil { + http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", report_id, e.Code()), http.StatusFound) + } else { + http.Redirect(w, r, fmt.Sprintf("/register-notifications-complete?report=%s", report_id), http.StatusFound) + } +} diff --git a/rmo/nuisance.go b/rmo/nuisance.go index c5de09a3..fbcba40f 100644 --- a/rmo/nuisance.go +++ b/rmo/nuisance.go @@ -10,6 +10,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/html" + "github.com/Gleipnir-Technology/nidus-sync/platform/report" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -84,7 +85,7 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { source_description := r.PostFormValue("source-description") additional_info := r.PostFormValue("additional-info") - public_id, err := GenerateReportID() + public_id, err := report.GenerateReportID() if err != nil { respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError) return diff --git a/rmo/pool.go b/rmo/pool.go index 6abb7efe..548f5c6c 100644 --- a/rmo/pool.go +++ b/rmo/pool.go @@ -14,6 +14,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/html" + "github.com/Gleipnir-Technology/nidus-sync/platform/report" "github.com/aarondl/opt/omit" "github.com/rs/zerolog/log" ) @@ -85,7 +86,7 @@ func postPool(w http.ResponseWriter, r *http.Request) { respondError(w, "Failed to parse zoom level", err, http.StatusBadRequest) return } - public_id, err := GenerateReportID() + public_id, err := report.GenerateReportID() if err != nil { respondError(w, "Failed to create pool report public ID", err, http.StatusInternalServerError) return diff --git a/rmo/quick.go b/rmo/quick.go index d001d358..becae56e 100644 --- a/rmo/quick.go +++ b/rmo/quick.go @@ -10,7 +10,6 @@ import ( "github.com/Gleipnir-Technology/bob" "github.com/Gleipnir-Technology/bob/dialect/psql" "github.com/Gleipnir-Technology/bob/dialect/psql/um" - "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -18,7 +17,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/h3utils" "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/Gleipnir-Technology/nidus-sync/platform" - "github.com/Gleipnir-Technology/nidus-sync/platform/text" + "github.com/Gleipnir-Technology/nidus-sync/platform/report" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -38,9 +37,8 @@ type District struct { } var ( - quickT = buildTemplate("quick", "base") - quickSubmitCompleteT = buildTemplate("quick-submit-complete", "base") - registerNotificationsCompleteT = buildTemplate("register-notifications-complete", "base") + quickT = buildTemplate("quick", "base") + quickSubmitCompleteT = buildTemplate("quick-submit-complete", "base") ) func getQuick(w http.ResponseWriter, r *http.Request) { @@ -146,7 +144,7 @@ func postQuick(w http.ResponseWriter, r *http.Request) { respondError(w, "Failed to create parse longitude", err, http.StatusBadRequest) return } - u, err := GenerateReportID() + u, err := report.GenerateReportID() if err != nil { respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError) return @@ -223,49 +221,3 @@ func postQuick(w http.ResponseWriter, r *http.Request) { tx.Commit(ctx) http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", u), http.StatusFound) } -func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { - err := r.ParseForm() - if err != nil { - respondError(w, "Failed to parse form", err, http.StatusBadRequest) - return - } - consent := r.PostFormValue("consent") - email := r.PostFormValue("email") - phone_str := r.PostFormValue("phone") - report_id := r.PostFormValue("report_id") - if consent != "on" { - respondError(w, "You must consent", nil, http.StatusBadRequest) - return - } - if email == "" && phone_str == "" { - http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", report_id), http.StatusFound) - return - } - phone, err := text.ParsePhoneNumber(phone_str) - result, err := psql.Update( - um.Table("publicreport.quick"), - um.SetCol("reporter_email").ToArg(email), - um.SetCol("reporter_phone").ToArg(phone), - um.Where(psql.Quote("public_id").EQ(psql.Arg(report_id))), - ).Exec(r.Context(), db.PGInstance.BobDB) - if err != nil { - respondError(w, "Failed to update report", err, http.StatusInternalServerError) - return - } - rowcount, err := result.RowsAffected() - if err != nil { - respondError(w, "Failed to get rows affected", err, http.StatusInternalServerError) - return - } - if email != "" { - background.ReportSubscriptionConfirmationEmail(email, report_id) - } - if phone_str != "" { - background.ReportSubscriptionConfirmationText(*phone, report_id) - } - if rowcount == 0 { - http.Redirect(w, r, fmt.Sprintf("/error?code=no-rows-affected&report=%s", report_id), http.StatusFound) - } else { - http.Redirect(w, r, fmt.Sprintf("/register-notifications-complete?report=%s", report_id), http.StatusFound) - } -} diff --git a/rmo/report.go b/rmo/report.go deleted file mode 100644 index a7d957d9..00000000 --- a/rmo/report.go +++ /dev/null @@ -1,33 +0,0 @@ -package rmo - -import ( - "crypto/rand" - "fmt" - "math/big" - "strings" -) - -// GenerateReportID creates a 12-character random string using only unambiguous -// capital letters and numbers -func GenerateReportID() (string, error) { - // Define character set (no O/0, I/l/1, 2/Z to avoid confusion) - const charset = "ABCDEFGHJKLMNPQRSTUVWXY3456789" - const length = 12 - - var builder strings.Builder - builder.Grow(length) - - // Use crypto/rand for secure randomness - for i := 0; i < length; i++ { - // Generate a random index within our charset - n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) - if err != nil { - return "", fmt.Errorf("failed to generate random number: %w", err) - } - - // Add the randomly selected character to our ID - builder.WriteByte(charset[n.Int64()]) - } - - return builder.String(), nil -} diff --git a/rmo/root.go b/rmo/root.go index daae08da..9e7ce3db 100644 --- a/rmo/root.go +++ b/rmo/root.go @@ -95,13 +95,6 @@ func makeContentURL() ContentURL { func makeURL(p string) string { return config.MakeURLReport("/%s", p) } -func postFormValueOrNone(r *http.Request, k string) string { - v := r.PostFormValue(k) - if v == "" { - return "none" - } - return v -} // Respond with an error that is visible to the user func respondError(w http.ResponseWriter, m string, e error, s int) { diff --git a/rmo/status.go b/rmo/status.go index e9467ae6..52b1cb67 100644 --- a/rmo/status.go +++ b/rmo/status.go @@ -16,7 +16,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/sql" "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" - "github.com/rs/zerolog/log" + //"github.com/rs/zerolog/log" "github.com/stephenafamo/scan" /* "github.com/Gleipnir-Technology/nidus-sync/db" @@ -96,27 +96,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) { } report_id := sanitizeReportID(report_id_str) report_id_str = formatReportID(report_id) - results, err := sql.PublicreportIDTable(report_id).All(r.Context(), db.PGInstance.BobDB) - if err != nil { - respondError(w, "Failed to query for report", err, http.StatusInternalServerError) - return - } - if len(results) != 1 { - log.Error().Int("count", len(results)).Str("report_id", report_id_str).Msg("Got too many results for report id. This is a programmer error.") - html.RenderOrError( - w, - Status, - ContentStatus{ - Error: "Sorry, server's confused", - ReportID: report_id_str, - }, - ) - } - result := results[0] - if result.ExistsSomewhere { - http.Redirect(w, r, fmt.Sprintf("/status/%s", report_id), http.StatusFound) - return - } + //some_report, e := report.FindSomeReport(r.Context(), report_id) html.RenderOrError( w, Status,