This in a pretty huge change. At a high level we're adding the concept of a 'contact' which is a person or organization that has zero or more contact methods (email, phone). This ended up cascading a number of changes, including critically to the publicreprt schema. In the end it seemed safer to get to the point where I'm confident we aren't using any of the old fields for storing reporter information (though I haven't deleted the columns yet) so I removed the code for defining those columns. At this point I think it's not possible for me to regenerate the bob schema due to the interdependencies between my various schemas, so the migration is well-and-truly happening.
192 lines
7 KiB
Go
192 lines
7 KiB
Go
package text
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/comms/text"
|
|
"github.com/Gleipnir-Technology/nidus-sync/config"
|
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
|
modelcomms "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/comms/model"
|
|
modelpublic "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/model"
|
|
modelpublicreport "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/model"
|
|
querycomms "github.com/Gleipnir-Technology/nidus-sync/db/query/comms"
|
|
querypublic "github.com/Gleipnir-Technology/nidus-sync/db/query/public"
|
|
querypublicreport "github.com/Gleipnir-Technology/nidus-sync/db/query/publicreport"
|
|
"github.com/Gleipnir-Technology/nidus-sync/lint"
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform/background"
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
func ensureInitialText(ctx context.Context, txn db.Ex, dst types.E164) error {
|
|
logs, err := querycomms.TextLogWelcomeFromDestination(ctx, txn, dst.PhoneString())
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to query text logs: %w", err)
|
|
}
|
|
if len(logs) > 0 {
|
|
return nil
|
|
}
|
|
return sendInitialText(ctx, txn, dst)
|
|
}
|
|
func resendInitialText(ctx context.Context, txn db.Ex, dst types.E164) error {
|
|
phone, err := querycomms.ContactPhoneFromE164(ctx, txn, dst.PhoneString())
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to find phone %s: %w", dst, err)
|
|
}
|
|
err = querycomms.ContactPhoneUpdateStopMessageID(ctx, txn, phone.E164, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to clear subscription on phone %s: %w", dst, err)
|
|
}
|
|
return nil
|
|
}
|
|
func sendInitialText(ctx context.Context, txn db.Ex, dst types.E164) error {
|
|
content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe"
|
|
_, err := sendTextDirect(ctx, txn, modelcomms.Textorigin_WebsiteAction, dst.PhoneString(), content, false, true)
|
|
if err != nil {
|
|
return fmt.Errorf("send text: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Begin the process of sending the text message, but only get as far as adding it to
|
|
// the database, then let the backend finish sending.
|
|
func sendTextBegin(ctx context.Context, txn db.Ex, user_id *int32, report_id *int32, destination types.E164, content string, type_ modelcomms.Textjobtype) (*int32, error) {
|
|
job, err := querycomms.TextJobInsert(ctx, txn, modelcomms.TextJob{
|
|
Content: content,
|
|
CreatorID: user_id,
|
|
Created: time.Now(),
|
|
Destination: destination.PhoneString(),
|
|
//ID:
|
|
ReportID: report_id,
|
|
Source: modelcomms.Textjobsource_Rmo,
|
|
Type: type_,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to add delayed text job: %w", err)
|
|
}
|
|
err = background.NewTextSend(ctx, txn, job.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new background job: %w", err)
|
|
}
|
|
return &job.ID, nil
|
|
}
|
|
func sendTextCommandResponse(ctx context.Context, txn db.Ex, dst types.E164, content string) error {
|
|
_, err := sendTextDirect(ctx, txn, modelcomms.Textorigin_CommandResponse, dst.PhoneString(), content, false, false)
|
|
return err
|
|
}
|
|
func sendTextComplete(ctx context.Context, job modelcomms.TextJob) error {
|
|
txn, err := db.BeginTxn(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("begin tx: %w", err)
|
|
}
|
|
defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")
|
|
dst, err := ParsePhoneNumber(job.Destination)
|
|
if err != nil {
|
|
return fmt.Errorf("parse phone: %w", err)
|
|
}
|
|
var origin modelcomms.Textorigin
|
|
switch job.Type {
|
|
case modelcomms.Textjobtype_ReportConfirmation:
|
|
origin = modelcomms.Textorigin_WebsiteAction
|
|
case modelcomms.Textjobtype_ReportMessage:
|
|
origin = modelcomms.Textorigin_District
|
|
default:
|
|
return fmt.Errorf("incomplete switch: %s", string(job.Type))
|
|
}
|
|
status, err := phoneStatus(ctx, *dst)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to check if subscribed: %w", err)
|
|
}
|
|
log.Debug().Str("phone status", string(status)).Str("destination", job.Destination).Send()
|
|
switch status {
|
|
case enums.CommsPhonestatustypeUnconfirmed:
|
|
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 enums.CommsPhonestatustypeStopped:
|
|
lint.LogOnErrCtx(func(ctx context.Context) error {
|
|
return resendInitialText(ctx, txn, *dst)
|
|
}, ctx, "resend initial text")
|
|
return nil
|
|
}
|
|
text_log, err := sendTextDirect(ctx, txn, origin, job.Destination, job.Content, true, false)
|
|
if err != nil {
|
|
return fmt.Errorf("send text direct: %w", err)
|
|
}
|
|
err = querycomms.TextJobComplete(ctx, txn, int64(job.ID))
|
|
if err != nil {
|
|
return fmt.Errorf("update job: %w", err)
|
|
}
|
|
if job.ReportID != nil {
|
|
creator_id := *job.CreatorID
|
|
report_id := *job.ReportID
|
|
log.Debug().Int32("creator", creator_id).Int32("report_id", report_id).Msg("Creating report entries for text message")
|
|
querypublic.ReportTextInsert(ctx, txn, modelpublic.ReportText{
|
|
CreatorID: creator_id,
|
|
ReportID: report_id,
|
|
TextLogID: text_log.ID,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("insert report_text: %w", err)
|
|
}
|
|
_, err = querypublicreport.ReportLogInsert(ctx, txn, modelpublicreport.ReportLog{
|
|
Created: time.Now(),
|
|
EmailLogID: nil,
|
|
// ID
|
|
ReportID: report_id,
|
|
TextLogID: &text_log.ID,
|
|
Type: modelpublicreport.Reportlogtype_MessageText,
|
|
UserID: &creator_id,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("insert report log: %w", err)
|
|
}
|
|
report, err := querypublicreport.ReportFromID(ctx, txn, int64(report_id))
|
|
if err != nil {
|
|
return fmt.Errorf("find public report: %w", err)
|
|
}
|
|
event.Updated(event.TypeRMOPublicReport, report.OrganizationID, report.PublicID)
|
|
} else {
|
|
log.Debug().Msg("no report info on text")
|
|
}
|
|
if err := txn.Commit(ctx); err != nil {
|
|
return fmt.Errorf("commit: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Send a text message and save the appropriate database records.
|
|
// Send immediately using the current goroutine
|
|
func sendTextDirect(ctx context.Context, txn db.Ex, origin modelcomms.Textorigin, destination, content string, is_visible_to_llm, is_welcome bool) (modelcomms.TextLog, error) {
|
|
text_log, err := querycomms.TextLogInsert(ctx, txn, modelcomms.TextLog{
|
|
Content: content,
|
|
Created: time.Now(),
|
|
Destination: destination,
|
|
IsVisibleToLlm: is_visible_to_llm,
|
|
IsWelcome: is_welcome,
|
|
Origin: origin,
|
|
Source: config.PhoneNumberReportStr,
|
|
TwilioSid: nil,
|
|
TwilioStatus: "",
|
|
})
|
|
if err != nil {
|
|
return modelcomms.TextLog{}, fmt.Errorf("insert text log: %w", err)
|
|
}
|
|
pid, err := text.SendText(ctx, config.VoipMSNumber, destination, content)
|
|
if err != nil {
|
|
return modelcomms.TextLog{}, fmt.Errorf("send text: %w", err)
|
|
}
|
|
err = querycomms.TextLogUpdate(ctx, txn, int64(text_log.ID), pid, "created")
|
|
if err != nil {
|
|
return modelcomms.TextLog{}, fmt.Errorf("update %w", err)
|
|
}
|
|
return text_log, nil
|
|
}
|