diff --git a/db/gen/nidus-sync/comms/model/contact_phone.go b/db/gen/nidus-sync/comms/model/contact_phone.go index 8f471443..6861edfe 100644 --- a/db/gen/nidus-sync/comms/model/contact_phone.go +++ b/db/gen/nidus-sync/comms/model/contact_phone.go @@ -8,11 +8,8 @@ package model type ContactPhone struct { - CanSms bool - ConfirmedMessageID *int32 - ContactID int32 - E164 string - IsSubscribed bool - StopMessageID *int32 - ID int32 `sql:"primary_key"` + ContactID int32 + E164 string + IsSubscribed bool + ID int32 `sql:"primary_key"` } diff --git a/db/gen/nidus-sync/comms/model/phone.go b/db/gen/nidus-sync/comms/model/phone.go index 14f36206..3f3dd440 100644 --- a/db/gen/nidus-sync/comms/model/phone.go +++ b/db/gen/nidus-sync/comms/model/phone.go @@ -8,8 +8,8 @@ package model type Phone struct { - E164 string `sql:"primary_key"` - IsSubscribed bool - Status Phonestatustype - CanSms bool + E164 string `sql:"primary_key"` + CanSms bool + ConfirmedMessageID *int32 + StopMessageID *int32 } diff --git a/db/gen/nidus-sync/comms/table/contact_phone.go b/db/gen/nidus-sync/comms/table/contact_phone.go index b2f3fb26..e58a8613 100644 --- a/db/gen/nidus-sync/comms/table/contact_phone.go +++ b/db/gen/nidus-sync/comms/table/contact_phone.go @@ -17,13 +17,10 @@ type contactPhoneTable struct { postgres.Table // Columns - CanSms postgres.ColumnBool - ConfirmedMessageID postgres.ColumnInteger - ContactID postgres.ColumnInteger - E164 postgres.ColumnString - IsSubscribed postgres.ColumnBool - StopMessageID postgres.ColumnInteger - ID postgres.ColumnInteger + ContactID postgres.ColumnInteger + E164 postgres.ColumnString + IsSubscribed postgres.ColumnBool + ID postgres.ColumnInteger AllColumns postgres.ColumnList MutableColumns postgres.ColumnList @@ -65,29 +62,23 @@ func newContactPhoneTable(schemaName, tableName, alias string) *ContactPhoneTabl func newContactPhoneTableImpl(schemaName, tableName, alias string) contactPhoneTable { var ( - CanSmsColumn = postgres.BoolColumn("can_sms") - ConfirmedMessageIDColumn = postgres.IntegerColumn("confirmed_message_id") - ContactIDColumn = postgres.IntegerColumn("contact_id") - E164Column = postgres.StringColumn("e164") - IsSubscribedColumn = postgres.BoolColumn("is_subscribed") - StopMessageIDColumn = postgres.IntegerColumn("stop_message_id") - IDColumn = postgres.IntegerColumn("id") - allColumns = postgres.ColumnList{CanSmsColumn, ConfirmedMessageIDColumn, ContactIDColumn, E164Column, IsSubscribedColumn, StopMessageIDColumn, IDColumn} - mutableColumns = postgres.ColumnList{CanSmsColumn, ConfirmedMessageIDColumn, ContactIDColumn, E164Column, IsSubscribedColumn, StopMessageIDColumn} - defaultColumns = postgres.ColumnList{IDColumn} + ContactIDColumn = postgres.IntegerColumn("contact_id") + E164Column = postgres.StringColumn("e164") + IsSubscribedColumn = postgres.BoolColumn("is_subscribed") + IDColumn = postgres.IntegerColumn("id") + allColumns = postgres.ColumnList{ContactIDColumn, E164Column, IsSubscribedColumn, IDColumn} + mutableColumns = postgres.ColumnList{ContactIDColumn, E164Column, IsSubscribedColumn} + defaultColumns = postgres.ColumnList{IDColumn} ) return contactPhoneTable{ Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns - CanSms: CanSmsColumn, - ConfirmedMessageID: ConfirmedMessageIDColumn, - ContactID: ContactIDColumn, - E164: E164Column, - IsSubscribed: IsSubscribedColumn, - StopMessageID: StopMessageIDColumn, - ID: IDColumn, + ContactID: ContactIDColumn, + E164: E164Column, + IsSubscribed: IsSubscribedColumn, + ID: IDColumn, AllColumns: allColumns, MutableColumns: mutableColumns, diff --git a/db/gen/nidus-sync/comms/table/phone.go b/db/gen/nidus-sync/comms/table/phone.go index 1f8258f5..dd856fba 100644 --- a/db/gen/nidus-sync/comms/table/phone.go +++ b/db/gen/nidus-sync/comms/table/phone.go @@ -17,10 +17,10 @@ type phoneTable struct { postgres.Table // Columns - E164 postgres.ColumnString - IsSubscribed postgres.ColumnBool - Status postgres.ColumnString - CanSms postgres.ColumnBool + E164 postgres.ColumnString + CanSms postgres.ColumnBool + ConfirmedMessageID postgres.ColumnInteger + StopMessageID postgres.ColumnInteger AllColumns postgres.ColumnList MutableColumns postgres.ColumnList @@ -62,23 +62,23 @@ func newPhoneTable(schemaName, tableName, alias string) *PhoneTable { func newPhoneTableImpl(schemaName, tableName, alias string) phoneTable { var ( - E164Column = postgres.StringColumn("e164") - IsSubscribedColumn = postgres.BoolColumn("is_subscribed") - StatusColumn = postgres.StringColumn("status") - CanSmsColumn = postgres.BoolColumn("can_sms") - allColumns = postgres.ColumnList{E164Column, IsSubscribedColumn, StatusColumn, CanSmsColumn} - mutableColumns = postgres.ColumnList{IsSubscribedColumn, StatusColumn, CanSmsColumn} - defaultColumns = postgres.ColumnList{} + E164Column = postgres.StringColumn("e164") + CanSmsColumn = postgres.BoolColumn("can_sms") + ConfirmedMessageIDColumn = postgres.IntegerColumn("confirmed_message_id") + StopMessageIDColumn = postgres.IntegerColumn("stop_message_id") + allColumns = postgres.ColumnList{E164Column, CanSmsColumn, ConfirmedMessageIDColumn, StopMessageIDColumn} + mutableColumns = postgres.ColumnList{CanSmsColumn, ConfirmedMessageIDColumn, StopMessageIDColumn} + defaultColumns = postgres.ColumnList{} ) return phoneTable{ Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns - E164: E164Column, - IsSubscribed: IsSubscribedColumn, - Status: StatusColumn, - CanSms: CanSmsColumn, + E164: E164Column, + CanSms: CanSmsColumn, + ConfirmedMessageID: ConfirmedMessageIDColumn, + StopMessageID: StopMessageIDColumn, AllColumns: allColumns, MutableColumns: mutableColumns, diff --git a/db/migrations/00154_comms_contact_phone_rework.sql b/db/migrations/00154_comms_contact_phone_rework.sql new file mode 100644 index 00000000..2474956f --- /dev/null +++ b/db/migrations/00154_comms_contact_phone_rework.sql @@ -0,0 +1,10 @@ +-- +goose Up +ALTER TABLE comms.contact_phone + DROP COLUMN can_sms, + DROP COLUMN confirmed_message_id, + DROP COLUMN stop_message_id; +ALTER TABLE comms.phone + ADD COLUMN confirmed_message_id INTEGER REFERENCES comms.text_log(id), + ADD COLUMN stop_message_id INTEGER REFERENCES comms.text_log(id), + DROP COLUMN is_subscribed, + DROP COLUMN status; diff --git a/db/query/comms/contact_phone.go b/db/query/comms/contact_phone.go index c995fbfa..ff53deec 100644 --- a/db/query/comms/contact_phone.go +++ b/db/query/comms/contact_phone.go @@ -27,26 +27,6 @@ func ContactPhoneFromE164(ctx context.Context, txn db.Ex, e164 string) (model.Co return db.ExecuteOneTx[model.ContactPhone](ctx, txn, statement) } -func ContactPhoneUpdateConfirmedMessageID(ctx context.Context, txn db.Ex, e164 string, message_id *int32) error { - statement := table.ContactPhone.UPDATE(). - SET(table.ContactPhone.ConfirmedMessageID.SET(postgres.IntExp(postgres.NULL))). - WHERE(table.ContactPhone.E164.EQ(postgres.String(e164))) - return db.ExecuteNoneTx(ctx, txn, statement) -} -func ContactPhoneUpdateStopMessageID(ctx context.Context, txn db.Ex, e164 string, message_id *int32) error { - /* - m := model.ContactPhone{} - m.StopMessageID = message_id - statement := table.ContactPhone.UPDATE( - table.ContactPhone.StopMessageID, - ).MODEL(m). - WHERE(table.ContactPhone.E164.EQ(postgres.String(e164))) - */ - statement := table.ContactPhone.UPDATE(). - SET(table.ContactPhone.StopMessageID.SET(postgres.IntExp(postgres.NULL))). - WHERE(table.ContactPhone.E164.EQ(postgres.String(e164))) - return db.ExecuteNoneTx(ctx, txn, statement) -} func ContactPhoneByContactIDs(ctx context.Context, txn db.Ex, contact_ids []int64) (result map[int64][]model.ContactPhone, err error) { sql_ids := make([]postgres.Expression, len(contact_ids)) for i, contact_id := range contact_ids { diff --git a/db/query/comms/phone.go b/db/query/comms/phone.go index 404fa45f..a4c91954 100644 --- a/db/query/comms/phone.go +++ b/db/query/comms/phone.go @@ -40,3 +40,34 @@ func PhoneFromE164(ctx context.Context, txn db.Ex, e164 string) (model.Phone, er WHERE(table.Phone.E164.EQ(postgres.String(e164))) return db.ExecuteOneTx[model.Phone](ctx, txn, statement) } +func PhonesFromE164s(ctx context.Context, txn db.Ex, e164s []string) ([]model.Phone, error) { + sql_ids := make([]postgres.Expression, len(e164s)) + for i, e164 := range e164s { + sql_ids[i] = postgres.String(e164) + } + statement := table.Phone.SELECT( + table.Phone.AllColumns, + ).FROM(table.Phone). + WHERE(table.Phone.E164.IN(sql_ids...)) + return db.ExecuteManyTx[model.Phone](ctx, txn, statement) +} +func PhoneUpdateConfirmedMessageID(ctx context.Context, txn db.Ex, e164 string, message_id *int32) error { + statement := table.Phone.UPDATE(). + SET(table.Phone.ConfirmedMessageID.SET(postgres.IntExp(postgres.NULL))). + WHERE(table.Phone.E164.EQ(postgres.String(e164))) + return db.ExecuteNoneTx(ctx, txn, statement) +} +func PhoneUpdateStopMessageID(ctx context.Context, txn db.Ex, e164 string, message_id *int32) error { + /* + m := model.Phone{} + m.StopMessageID = message_id + statement := table.Phone.UPDATE( + table.Phone.StopMessageID, + ).MODEL(m). + WHERE(table.Phone.E164.EQ(postgres.String(e164))) + */ + statement := table.Phone.UPDATE(). + SET(table.Phone.StopMessageID.SET(postgres.IntExp(postgres.NULL))). + WHERE(table.Phone.E164.EQ(postgres.String(e164))) + return db.ExecuteNoneTx(ctx, txn, statement) +} diff --git a/platform/contact.go b/platform/contact.go index bb7a4fb8..93ac931a 100644 --- a/platform/contact.go +++ b/platform/contact.go @@ -29,7 +29,20 @@ func ContactsForOrganization(ctx context.Context, org_id int32) (results []types if err != nil { return results, fmt.Errorf("by contact ids: %w", err) } - + e164s := make([]string, 0) + for _, v := range contact_phones_by_contact_id { + for _, p := range v { + e164s = append(e164s, p.E164) + } + } + phones, err := querycomms.PhonesFromE164s(ctx, txn, e164s) + if err != nil { + return results, fmt.Errorf("phones from e164: %w", err) + } + phones_by_e164 := make(map[string]modelcomms.Phone, len(phones)) + for _, p := range phones { + phones_by_e164[p.E164] = p + } results = make([]types.Contact, 0) for _, row := range rows { // Exclude the magic Nidus contact @@ -44,9 +57,10 @@ func ContactsForOrganization(ctx context.Context, org_id int32) (results []types contact_phones := contact_phones_by_contact_id[int64(row.ID)] phones := make([]types.Phone, len(contact_phones)) for i, p := range contact_phones { + phone := phones_by_e164[p.E164] phones[i] = types.Phone{ - E164: p.E164, - CanSMS: p.CanSms, + E164: phone.E164, + CanSMS: phone.CanSms, } } if row.Name != "" || len(contact_phones) > 0 || len(contact_emails) > 0 { diff --git a/platform/publicreport/contact.go b/platform/publicreport/contact.go index b27ba275..60ec2173 100644 --- a/platform/publicreport/contact.go +++ b/platform/publicreport/contact.go @@ -5,6 +5,7 @@ import ( "fmt" "source.gleipnir.technology/Gleipnir/nidus-sync/db" + modelcomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/comms/model" querycomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/comms" "source.gleipnir.technology/Gleipnir/nidus-sync/platform/types" ) @@ -22,6 +23,20 @@ func ContactSimplesFromIDs(ctx context.Context, txn db.Ex, contact_ids []int64) if err != nil { return nil, fmt.Errorf("contact phones from ids: %w", err) } + e164s := make([]string, 0) + for _, v := range contact_phones { + for _, p := range v { + e164s = append(e164s, p.E164) + } + } + phones, err := querycomms.PhonesFromE164s(ctx, txn, e164s) + if err != nil { + return nil, fmt.Errorf("contact phones from ids: %w", err) + } + phones_by_e164 := make(map[string]modelcomms.Phone, 0) + for _, p := range phones { + phones_by_e164[p.E164] = p + } results := make([]types.ContactSimple, len(contact_ids)) for i, contact := range contacts { @@ -29,7 +44,7 @@ func ContactSimplesFromIDs(ctx context.Context, txn db.Ex, contact_ids []int64) if !ok { return nil, fmt.Errorf("no emails for contact %d", contact.ID) } - phones, ok := contact_phones[int64(contact.ID)] + cps, ok := contact_phones[int64(contact.ID)] if !ok { return nil, fmt.Errorf("no phones for contact %d", contact.ID) } @@ -38,8 +53,9 @@ func ContactSimplesFromIDs(ctx context.Context, txn db.Ex, contact_ids []int64) email_string = emails[0].Address } phone_simple := types.PhoneSimple{} - if len(phones) > 0 { - phone := phones[0] + if len(cps) > 0 { + contact_phone := cps[0] + phone := phones_by_e164[contact_phone.E164] phone_simple = types.PhoneSimple{ CanSMS: phone.CanSms, Number: phone.E164, diff --git a/platform/publicreport/notification.go b/platform/publicreport/notification.go index a90cb776..f3f4e51d 100644 --- a/platform/publicreport/notification.go +++ b/platform/publicreport/notification.go @@ -159,18 +159,24 @@ func saveReporterEmail(ctx context.Context, txn db.Ex, contact_id int32, email_a return nil } -func saveReporterPhone(ctx context.Context, txn db.Ex, contact_id int32, phone *types.E164, can_sms bool) error { - if phone == nil { +func saveReporterPhone(ctx context.Context, txn db.Ex, contact_id int32, number *types.E164, can_sms bool) error { + if number == nil { return nil } - p, err := querycomms.ContactPhoneInsert(ctx, txn, modelcomms.ContactPhone{ + _, err := querycomms.PhoneInsertIfNotExists(ctx, txn, modelcomms.Phone{ + E164: number.PhoneString(), CanSms: can_sms, ConfirmedMessageID: nil, - ContactID: contact_id, - E164: phone.PhoneString(), - IsSubscribed: false, StopMessageID: nil, }) + if err != nil { + return fmt.Errorf("insert phone if not exists: %w", err) + } + p, err := querycomms.ContactPhoneInsert(ctx, txn, modelcomms.ContactPhone{ + ContactID: contact_id, + E164: number.PhoneString(), + IsSubscribed: false, + }) if err != nil { return fmt.Errorf("contact add phone: %w", err) } diff --git a/platform/text/phone_number.go b/platform/text/phone_number.go index f9af38d8..d9689cd2 100644 --- a/platform/text/phone_number.go +++ b/platform/text/phone_number.go @@ -15,21 +15,17 @@ func EnsureInDB(ctx context.Context, txn db.Ex, contact modelcomms.Contact, dst } func ensureInDB(ctx context.Context, txn db.Ex, contact modelcomms.Contact, destination string) (err error) { contact_phone := modelcomms.ContactPhone{ - CanSms: true, - ConfirmedMessageID: nil, - ContactID: contact.ID, - E164: destination, - IsSubscribed: false, - StopMessageID: nil, + ContactID: contact.ID, + E164: destination, } _, err = querycomms.ContactPhoneInsert(ctx, txn, contact_phone) return err } func ensurePhoneInDB(ctx context.Context, txn db.Ex, number *types.E164) (modelcomms.Phone, error) { return querycomms.PhoneInsertIfNotExists(ctx, txn, modelcomms.Phone{ - CanSms: false, - E164: number.PhoneString(), - IsSubscribed: false, - Status: modelcomms.Phonestatustype_Unconfirmed, + CanSms: false, + ConfirmedMessageID: nil, + E164: number.PhoneString(), + StopMessageID: nil, }) } diff --git a/platform/text/send.go b/platform/text/send.go index 1aa99c0f..2bc4e179 100644 --- a/platform/text/send.go +++ b/platform/text/send.go @@ -36,7 +36,7 @@ func resendInitialText(ctx context.Context, txn db.Ex, dst types.E164) error { if err != nil { return fmt.Errorf("Failed to find phone %s: %w", dst, err) } - err = querycomms.ContactPhoneUpdateStopMessageID(ctx, txn, phone.E164, nil) + err = querycomms.PhoneUpdateStopMessageID(ctx, txn, phone.E164, nil) if err != nil { return fmt.Errorf("Failed to clear subscription on phone %s: %w", dst, err) } @@ -100,17 +100,14 @@ func sendTextComplete(ctx context.Context, job modelcomms.TextJob) error { if err != nil { return fmt.Errorf("destination phone from e164: %w", err) } - log.Debug().Str("phone status", string(destination.Status)).Str("destination", job.Destination).Send() - switch destination.Status { - case modelcomms.Phonestatustype_Unconfirmed: + if destination.ConfirmedMessageID == nil { err := ensureInitialText(ctx, txn, *dst) if err != nil { return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) } return nil - //case enums.CommsPhonestatustypeOkToSend: - // allow to drop through - case modelcomms.Phonestatustype_Stopped: + } + if destination.StopMessageID != nil { lint.LogOnErrCtx(func(ctx context.Context) error { return resendInitialText(ctx, txn, *dst) }, ctx, "resend initial text") diff --git a/platform/text/text.go b/platform/text/text.go index 471cbd9b..18c0639e 100644 --- a/platform/text/text.go +++ b/platform/text/text.go @@ -41,7 +41,7 @@ func HandleTextMessage(ctx context.Context, source string, destination string, c if err != nil { return fmt.Errorf("ensure source in DB: %w", err) } - is_visible_to_llm := s.Status != modelcomms.Phonestatustype_Unconfirmed + is_visible_to_llm := s.ConfirmedMessageID != nil l, err := querycomms.TextLogInsert(ctx, txn, modelcomms.TextLog{ Content: content, @@ -83,17 +83,17 @@ func respondText(ctx context.Context, log_id int32) error { return fmt.Errorf("parse source: %w", err) } - contact_phone, err := querycomms.ContactPhoneFromE164(ctx, txn, src.PhoneString()) + phone, err := querycomms.PhoneFromE164(ctx, txn, src.PhoneString()) if err != nil { return fmt.Errorf("Failed to get contact phone") } body_l := strings.TrimSpace(strings.ToLower(l.Content)) // If the user isn't confirmed for sending regular texts ensure they get a reprompt - if contact_phone.ConfirmedMessageID == nil { + if phone.ConfirmedMessageID == nil { switch body_l { case "yes": - err = querycomms.ContactPhoneUpdateConfirmedMessageID(ctx, txn, src.PhoneString(), &l.ID) + err = querycomms.PhoneUpdateConfirmedMessageID(ctx, txn, src.PhoneString(), &l.ID) if err != nil { return fmt.Errorf("set phone confirmed message ID: %w", err) } @@ -119,7 +119,7 @@ func respondText(ctx context.Context, log_id int32) error { } switch body_l { case "stop": - err = querycomms.ContactPhoneUpdateStopMessageID(ctx, txn, src.PhoneString(), &l.ID) + err = querycomms.PhoneUpdateStopMessageID(ctx, txn, src.PhoneString(), &l.ID) if err != nil { return fmt.Errorf("set phone stop message ID: %w", err) } diff --git a/platform/types/contact.go b/platform/types/contact.go index 01852482..5b79c491 100644 --- a/platform/types/contact.go +++ b/platform/types/contact.go @@ -19,6 +19,7 @@ type ContactSimple struct { type Phone struct { E164 string `json:"e164"` CanSMS bool `json:"can_sms"` + Status string `json:"status"` } type PhoneSimple struct { CanSMS bool `json:"can_sms"`