This makes sense because there will naturally be cases where multiple districts have the same phone number mapped to different contacts.
188 lines
6.8 KiB
Go
188 lines
6.8 KiB
Go
package text
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/comms/text"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/config"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/db"
|
|
modelcomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/comms/model"
|
|
modelpublic "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/public/model"
|
|
modelpublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/publicreport/model"
|
|
querycomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/comms"
|
|
querypublic "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/public"
|
|
querypublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/publicreport"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/lint"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/platform/background"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/platform/event"
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/platform/types"
|
|
)
|
|
|
|
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.PhoneUpdateStopMessageID(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))
|
|
}
|
|
destination, err := querycomms.PhoneFromE164(ctx, txn, dst.PhoneString())
|
|
if err != nil {
|
|
return fmt.Errorf("destination phone from e164: %w", err)
|
|
}
|
|
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
|
|
}
|
|
if destination.StopMessageID != nil {
|
|
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
|
|
}
|