2026-03-18 15:36:20 +00:00
package text
import (
"context"
"fmt"
"time"
"github.com/rs/zerolog/log"
2026-05-19 15:33:57 +00:00
"source.gleipnir.technology/Gleipnir/nidus-sync/comms/text"
"source.gleipnir.technology/Gleipnir/nidus-sync/config"
"source.gleipnir.technology/Gleipnir/nidus-sync/db"
"source.gleipnir.technology/Gleipnir/nidus-sync/db/enums"
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"
2026-03-18 15:36:20 +00:00
)
2026-05-15 16:58:28 +00:00
func ensureInitialText ( ctx context . Context , txn db . Ex , dst types . E164 ) error {
logs , err := querycomms . TextLogWelcomeFromDestination ( ctx , txn , dst . PhoneString ( ) )
2026-03-18 15:36:20 +00:00
if err != nil {
return fmt . Errorf ( "Failed to query text logs: %w" , err )
}
2026-05-15 16:58:28 +00:00
if len ( logs ) > 0 {
2026-03-18 15:36:20 +00:00
return nil
}
return sendInitialText ( ctx , txn , dst )
}
2026-05-15 16:58:28 +00:00
func resendInitialText ( ctx context . Context , txn db . Ex , dst types . E164 ) error {
phone , err := querycomms . ContactPhoneFromE164 ( ctx , txn , dst . PhoneString ( ) )
2026-03-18 15:36:20 +00:00
if err != nil {
return fmt . Errorf ( "Failed to find phone %s: %w" , dst , err )
}
2026-05-15 16:58:28 +00:00
err = querycomms . ContactPhoneUpdateStopMessageID ( ctx , txn , phone . E164 , nil )
2026-03-18 15:36:20 +00:00
if err != nil {
return fmt . Errorf ( "Failed to clear subscription on phone %s: %w" , dst , err )
}
return nil
}
2026-05-15 16:58:28 +00:00
func sendInitialText ( ctx context . Context , txn db . Ex , dst types . E164 ) error {
2026-03-18 15:36:20 +00:00
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"
2026-05-15 16:58:28 +00:00
_ , err := sendTextDirect ( ctx , txn , modelcomms . Textorigin_WebsiteAction , dst . PhoneString ( ) , content , false , true )
2026-03-18 15:36:20 +00:00
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.
2026-05-15 16:58:28 +00:00
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 ( ) ,
2026-03-18 15:36:20 +00:00
//ID:
2026-05-15 16:58:28 +00:00
ReportID : report_id ,
Source : modelcomms . Textjobsource_Rmo ,
Type : type_ ,
} )
2026-03-18 15:36:20 +00:00
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
}
2026-05-15 16:58:28 +00:00
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 )
2026-03-18 15:36:20 +00:00
return err
}
2026-05-15 16:58:28 +00:00
func sendTextComplete ( ctx context . Context , job modelcomms . TextJob ) error {
txn , err := db . BeginTxn ( ctx )
2026-04-17 22:53:23 +00:00
if err != nil {
return fmt . Errorf ( "begin tx: %w" , err )
}
lint: fix remaining errcheck issues across multiple files
- Fix renderShim errcheck in district.go, image.go
- Fix txn.Rollback/Commit in publicreport.go, notification, review, signal, upload
- Fix addError calls in csv/flyover.go, csv/pool.go
- Fix cW/write calls in logger.go, recoverer.go, voipms.go
- Fix resendInitialText, handleWaitingTextJobs, setPhoneStatus in text/send.go, text.go
- Fix populateDistrictURI/populateReportURI in resource files
2026-05-09 03:06:56 +00:00
defer lint . LogOnErrRollback ( txn . Rollback , ctx , "rollback" )
2026-03-18 15:36:20 +00:00
dst , err := ParsePhoneNumber ( job . Destination )
if err != nil {
return fmt . Errorf ( "parse phone: %w" , err )
}
2026-05-15 16:58:28 +00:00
var origin modelcomms . Textorigin
2026-03-18 15:36:20 +00:00
switch job . Type {
2026-05-15 16:58:28 +00:00
case modelcomms . Textjobtype_ReportConfirmation :
origin = modelcomms . Textorigin_WebsiteAction
case modelcomms . Textjobtype_ReportMessage :
origin = modelcomms . Textorigin_District
2026-03-18 15:36:20 +00:00
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
2026-03-18 18:56:51 +00:00
//case enums.CommsPhonestatustypeOkToSend:
// allow to drop through
2026-03-18 15:36:20 +00:00
case enums . CommsPhonestatustypeStopped :
lint: fix remaining errcheck issues across multiple files
- Fix renderShim errcheck in district.go, image.go
- Fix txn.Rollback/Commit in publicreport.go, notification, review, signal, upload
- Fix addError calls in csv/flyover.go, csv/pool.go
- Fix cW/write calls in logger.go, recoverer.go, voipms.go
- Fix resendInitialText, handleWaitingTextJobs, setPhoneStatus in text/send.go, text.go
- Fix populateDistrictURI/populateReportURI in resource files
2026-05-09 03:06:56 +00:00
lint . LogOnErrCtx ( func ( ctx context . Context ) error {
return resendInitialText ( ctx , txn , * dst )
} , ctx , "resend initial text" )
2026-03-18 18:56:51 +00:00
return nil
2026-03-18 15:36:20 +00:00
}
text_log , err := sendTextDirect ( ctx , txn , origin , job . Destination , job . Content , true , false )
if err != nil {
return fmt . Errorf ( "send text direct: %w" , err )
}
2026-05-15 16:58:28 +00:00
err = querycomms . TextJobComplete ( ctx , txn , int64 ( job . ID ) )
2026-03-18 15:36:20 +00:00
if err != nil {
return fmt . Errorf ( "update job: %w" , err )
}
2026-05-15 16:58:28 +00:00
if job . ReportID != nil {
creator_id := * job . CreatorID
report_id := * job . ReportID
2026-03-18 18:56:51 +00:00
log . Debug ( ) . Int32 ( "creator" , creator_id ) . Int32 ( "report_id" , report_id ) . Msg ( "Creating report entries for text message" )
2026-05-15 16:58:28 +00:00
querypublic . ReportTextInsert ( ctx , txn , modelpublic . ReportText {
CreatorID : creator_id ,
ReportID : report_id ,
TextLogID : text_log . ID ,
} )
2026-03-18 15:36:20 +00:00
if err != nil {
return fmt . Errorf ( "insert report_text: %w" , err )
}
2026-05-15 16:58:28 +00:00
_ , err = querypublicreport . ReportLogInsert ( ctx , txn , modelpublicreport . ReportLog {
Created : time . Now ( ) ,
EmailLogID : nil ,
2026-03-18 18:56:51 +00:00
// ID
2026-05-15 16:58:28 +00:00
ReportID : report_id ,
TextLogID : & text_log . ID ,
Type : modelpublicreport . Reportlogtype_MessageText ,
UserID : & creator_id ,
} )
2026-05-09 02:35:55 +00:00
if err != nil {
return fmt . Errorf ( "insert report log: %w" , err )
}
2026-05-15 16:58:28 +00:00
report , err := querypublicreport . ReportFromID ( ctx , txn , int64 ( report_id ) )
2026-03-18 18:56:51 +00:00
if err != nil {
return fmt . Errorf ( "find public report: %w" , err )
}
2026-04-13 16:43:15 +00:00
event . Updated ( event . TypeRMOPublicReport , report . OrganizationID , report . PublicID )
2026-03-18 18:56:51 +00:00
} else {
log . Debug ( ) . Msg ( "no report info on text" )
2026-03-18 15:36:20 +00:00
}
lint: fix remaining errcheck issues across multiple files
- Fix renderShim errcheck in district.go, image.go
- Fix txn.Rollback/Commit in publicreport.go, notification, review, signal, upload
- Fix addError calls in csv/flyover.go, csv/pool.go
- Fix cW/write calls in logger.go, recoverer.go, voipms.go
- Fix resendInitialText, handleWaitingTextJobs, setPhoneStatus in text/send.go, text.go
- Fix populateDistrictURI/populateReportURI in resource files
2026-05-09 03:06:56 +00:00
if err := txn . Commit ( ctx ) ; err != nil {
return fmt . Errorf ( "commit: %w" , err )
}
2026-03-18 15:36:20 +00:00
return nil
}
// Send a text message and save the appropriate database records.
// Send immediately using the current goroutine
2026-05-15 16:58:28 +00:00
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 : "" ,
} )
2026-03-18 15:36:20 +00:00
if err != nil {
2026-05-15 16:58:28 +00:00
return modelcomms . TextLog { } , fmt . Errorf ( "insert text log: %w" , err )
2026-03-18 15:36:20 +00:00
}
pid , err := text . SendText ( ctx , config . VoipMSNumber , destination , content )
if err != nil {
2026-05-15 16:58:28 +00:00
return modelcomms . TextLog { } , fmt . Errorf ( "send text: %w" , err )
2026-03-18 15:36:20 +00:00
}
2026-05-15 16:58:28 +00:00
err = querycomms . TextLogUpdate ( ctx , txn , int64 ( text_log . ID ) , pid , "created" )
2026-03-18 15:36:20 +00:00
if err != nil {
2026-05-15 16:58:28 +00:00
return modelcomms . TextLog { } , fmt . Errorf ( "update %w" , err )
2026-03-18 15:36:20 +00:00
}
return text_log , nil
}