Overhaul text messaging system to be like emails
It's a better system for organization and makes it so we can have better logs about what gets sent.
This commit is contained in:
parent
5e9c0d9f11
commit
c0b6398de2
19 changed files with 577 additions and 219 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/text"
|
||||
)
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
|
|
@ -14,7 +15,7 @@ func Start(ctx context.Context) {
|
|||
|
||||
channelJobAudio = make(chan jobAudio, 100) // Buffered channel to prevent blocking
|
||||
channelJobEmail = make(chan email.Job, 100) // Buffered channel to prevent blocking
|
||||
channelJobText = make(chan jobText, 100) // Buffered channel to prevent blocking
|
||||
channelJobText = make(chan text.Job, 100) // Buffered channel to prevent blocking
|
||||
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
|
|
|
|||
|
|
@ -2,34 +2,23 @@ package background
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/text"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var channelJobText chan jobText
|
||||
var channelJobText chan text.Job
|
||||
|
||||
func ReportSubscriptionConfirmationText(destination comms.E164, report_id string) {
|
||||
enqueueJobText(jobText{
|
||||
Destination: destination,
|
||||
ReportID: report_id,
|
||||
Source: config.RMOPhoneNumber,
|
||||
Type: enums.CommsMessagetypetextReportSubscriptionConfirmation,
|
||||
})
|
||||
func ReportSubscriptionConfirmationText(destination text.E164, report_id string) {
|
||||
enqueueJobText(text.NewJobReportSubscriptionConfirmation(
|
||||
destination,
|
||||
report_id,
|
||||
config.RMOPhoneNumber,
|
||||
))
|
||||
}
|
||||
|
||||
type jobText struct {
|
||||
Destination comms.E164
|
||||
ReportID string
|
||||
Source comms.E164
|
||||
Type enums.CommsMessagetypetext
|
||||
}
|
||||
|
||||
func enqueueJobText(job jobText) {
|
||||
func enqueueJobText(job text.Job) {
|
||||
select {
|
||||
case channelJobText <- job:
|
||||
log.Info().Msg("Enqueued text job")
|
||||
|
|
@ -38,7 +27,7 @@ func enqueueJobText(job jobText) {
|
|||
}
|
||||
}
|
||||
|
||||
func startWorkerText(ctx context.Context, channel chan jobText) {
|
||||
func startWorkerText(ctx context.Context, channel chan text.Job) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
|
|
@ -46,29 +35,8 @@ func startWorkerText(ctx context.Context, channel chan jobText) {
|
|||
log.Info().Msg("Email worker shutting down.")
|
||||
return
|
||||
case job := <-channel:
|
||||
err := jobProcessText(job)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("type", string(job.Type)).Msg("Error processing text message job")
|
||||
}
|
||||
text.Handle(ctx, job)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func jobProcessText(job jobText) error {
|
||||
var message string
|
||||
switch job.Type {
|
||||
case enums.CommsMessagetypetextInitialContact:
|
||||
message = "This is Report Mosquitoes Online. We just got your number. Text \"YES\" to get texts, or \"STOP\" to stap."
|
||||
case enums.CommsMessagetypetextReportSubscriptionConfirmation:
|
||||
message = "Thanks for submitting a mosquito report. Text for any questions. We'll send you updates as we get them."
|
||||
default:
|
||||
return errors.New("No idea what message to send")
|
||||
}
|
||||
err := comms.SendText(job.Source, job.Destination, message)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to send text message")
|
||||
return fmt.Errorf("Failed to send message '%s' to '%s'", job.Type, job.Destination)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
93
comms/text/db.go
Normal file
93
comms/text/db.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stephenafamo/bob/types/pgtypes"
|
||||
)
|
||||
|
||||
func convertToPGData(data map[string]string) pgtypes.HStore {
|
||||
result := pgtypes.HStore{}
|
||||
for k, v := range data {
|
||||
result[k] = sql.Null[string]{V: v, Valid: true}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ensureInDB(ctx context.Context, destination string) (err error) {
|
||||
_, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination)
|
||||
if err != nil {
|
||||
// doesn't exist
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
_, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{
|
||||
E164: omit.From(destination),
|
||||
IsSubscribed: omit.From(false),
|
||||
}).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert new phone contact: %w", err)
|
||||
}
|
||||
log.Info().Str("phone", destination).Msg("Added text to the comms database")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unexpected error searching for phone contact: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin) (err error) {
|
||||
_, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{
|
||||
//ID:
|
||||
Content: omit.From(content),
|
||||
Created: omit.From(time.Now()),
|
||||
Destination: omit.From(destination),
|
||||
Origin: omit.From(origin),
|
||||
Source: omit.From(source),
|
||||
}).One(ctx, db.PGInstance.BobDB)
|
||||
|
||||
return err
|
||||
}
|
||||
func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string {
|
||||
if m == nil || len(m) == 0 {
|
||||
// Return hash of empty string for empty maps
|
||||
emptyHash := sha256.Sum256([]byte(""))
|
||||
return hex.EncodeToString(emptyHash[:])
|
||||
}
|
||||
|
||||
// Get and sort keys for deterministic ordering
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// Build a string with all key-value pairs
|
||||
var sb strings.Builder
|
||||
// Add type first
|
||||
sb.WriteString(fmt.Sprintf("type:%s,", t))
|
||||
for _, k := range keys {
|
||||
sb.WriteString(k)
|
||||
sb.WriteString(":") // Separator between key and value
|
||||
sb.WriteString(m[k])
|
||||
sb.WriteString(",") // Separator between pairs
|
||||
}
|
||||
|
||||
// Compute SHA-256 hash
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(sb.String()))
|
||||
hashBytes := hasher.Sum(nil)
|
||||
|
||||
// Convert to hex string and return
|
||||
return hex.EncodeToString(hashBytes)
|
||||
}
|
||||
39
comms/text/job.go
Normal file
39
comms/text/job.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type MessageType int
|
||||
|
||||
const (
|
||||
ReportSubscription MessageType = iota
|
||||
)
|
||||
|
||||
type Job interface {
|
||||
content() string
|
||||
destination() string
|
||||
messageType() MessageType
|
||||
messageTypeName() string
|
||||
source() string
|
||||
}
|
||||
|
||||
func Handle(ctx context.Context, job Job) {
|
||||
var err error
|
||||
switch job.messageType() {
|
||||
case ReportSubscription:
|
||||
err = sendReportSubscription(ctx, job)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("dest", job.destination()).Str("type", string(job.messageTypeName())).Msg("Error processing email")
|
||||
return
|
||||
}
|
||||
/*
|
||||
case enums.CommsMessagetypeemailReportStatusScheduled:
|
||||
case enums.CommsMessagetypeemailReportStatusComplete:
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
56
comms/text/report-subscription.go
Normal file
56
comms/text/report-subscription.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/nyaruka/phonenumbers"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func NewJobReportSubscriptionConfirmation(
|
||||
destination E164,
|
||||
report_id string,
|
||||
source E164) jobReportSubscription {
|
||||
return jobReportSubscription{
|
||||
dst: destination,
|
||||
reportID: report_id,
|
||||
src: source,
|
||||
}
|
||||
}
|
||||
|
||||
type jobReportSubscription struct {
|
||||
dst E164
|
||||
reportID string
|
||||
src E164
|
||||
}
|
||||
|
||||
func (j jobReportSubscription) content() string {
|
||||
return fmt.Sprintf("Thanks for submitting mosquito report %s. Text for any questions. We'll send you updates as we get them.", j.reportID)
|
||||
}
|
||||
func (j jobReportSubscription) destination() string {
|
||||
return phonenumbers.Format(&j.dst, phonenumbers.E164)
|
||||
}
|
||||
func (j jobReportSubscription) messageType() MessageType {
|
||||
return ReportSubscription
|
||||
}
|
||||
func (j jobReportSubscription) messageTypeName() string {
|
||||
return "report-subscription"
|
||||
}
|
||||
func (j jobReportSubscription) source() string {
|
||||
return phonenumbers.Format(&j.src, phonenumbers.E164)
|
||||
}
|
||||
|
||||
func sendReportSubscription(ctx context.Context, job Job) error {
|
||||
j, ok := job.(jobReportSubscription)
|
||||
if !ok {
|
||||
return fmt.Errorf("job is not for report subscription confirmation")
|
||||
}
|
||||
|
||||
err := sendText(ctx, j.src, j.dst, j.content(), enums.CommsTextoriginWebsiteAction)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to send report subscription confirmation: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package comms
|
||||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/nyaruka/phonenumbers"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/twilio/twilio-go"
|
||||
|
|
@ -17,16 +19,23 @@ func ParsePhoneNumber(input string) (*E164, error) {
|
|||
return phonenumbers.Parse(input, "US")
|
||||
}
|
||||
|
||||
func SendText(source E164, destination E164, message string) error {
|
||||
log.Info().Msg("Sending text message...")
|
||||
// Make sure TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN exists in your environment
|
||||
func sendText(ctx context.Context, source E164, destination E164, message string, origin enums.CommsTextorigin) error {
|
||||
src := phonenumbers.Format(&source, phonenumbers.E164)
|
||||
dest := phonenumbers.Format(&destination, phonenumbers.E164)
|
||||
err := ensureInDB(ctx, dest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err)
|
||||
}
|
||||
err = insertTextLog(ctx, message, dest, src, origin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert text message in the DB: %w", err)
|
||||
}
|
||||
client := twilio.NewRestClient()
|
||||
|
||||
params := &twilioApi.CreateMessageParams{}
|
||||
params.SetMessagingServiceSid(config.TwilioMessagingServiceSID)
|
||||
|
||||
params.SetBody(message)
|
||||
dest := phonenumbers.Format(&destination, phonenumbers.E164)
|
||||
params.SetTo(dest)
|
||||
resp, err := client.Api.CreateMessage(params)
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ var CommsTextLogErrors = &commsTextLogErrors{
|
|||
ErrUniqueTextLogPkey: &UniqueConstraintError{
|
||||
schema: "comms",
|
||||
table: "text_log",
|
||||
columns: []string{"destination", "source", "type"},
|
||||
columns: []string{"id"},
|
||||
s: "text_log_pkey",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ var OrganizationErrors = &organizationErrors{
|
|||
s: "organization_import_district_gid_key",
|
||||
},
|
||||
|
||||
ErrUniqueOrganizationSlugKey: &UniqueConstraintError{
|
||||
schema: "",
|
||||
table: "organization",
|
||||
columns: []string{"slug"},
|
||||
s: "organization_slug_key",
|
||||
},
|
||||
|
||||
ErrUniqueOrganizationWebsiteKey: &UniqueConstraintError{
|
||||
schema: "",
|
||||
table: "organization",
|
||||
|
|
@ -31,5 +38,7 @@ type organizationErrors struct {
|
|||
|
||||
ErrUniqueOrganizationImportDistrictGidKey *UniqueConstraintError
|
||||
|
||||
ErrUniqueOrganizationSlugKey *UniqueConstraintError
|
||||
|
||||
ErrUniqueOrganizationWebsiteKey *UniqueConstraintError
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,15 @@ var CommsTextLogs = Table[
|
|||
Schema: "comms",
|
||||
Name: "text_log",
|
||||
Columns: commsTextLogColumns{
|
||||
Content: column{
|
||||
Name: "content",
|
||||
DBType: "text",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Created: column{
|
||||
Name: "created",
|
||||
DBType: "timestamp without time zone",
|
||||
|
|
@ -33,18 +42,27 @@ var CommsTextLogs = Table[
|
|||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Source: column{
|
||||
Name: "source",
|
||||
DBType: "text",
|
||||
ID: column{
|
||||
Name: "id",
|
||||
DBType: "integer",
|
||||
Default: "nextval('comms.text_log_id_seq'::regclass)",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Origin: column{
|
||||
Name: "origin",
|
||||
DBType: "comms.textorigin",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Type: column{
|
||||
Name: "type",
|
||||
DBType: "comms.messagetypetext",
|
||||
Source: column{
|
||||
Name: "source",
|
||||
DBType: "text",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
|
|
@ -58,24 +76,14 @@ var CommsTextLogs = Table[
|
|||
Name: "text_log_pkey",
|
||||
Columns: []indexColumn{
|
||||
{
|
||||
Name: "destination",
|
||||
Desc: null.FromCond(false, true),
|
||||
IsExpression: false,
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Desc: null.FromCond(false, true),
|
||||
IsExpression: false,
|
||||
},
|
||||
{
|
||||
Name: "type",
|
||||
Name: "id",
|
||||
Desc: null.FromCond(false, true),
|
||||
IsExpression: false,
|
||||
},
|
||||
},
|
||||
Unique: true,
|
||||
Comment: "",
|
||||
NullsFirst: []bool{false, false, false},
|
||||
NullsFirst: []bool{false},
|
||||
NullsDistinct: false,
|
||||
Where: "",
|
||||
Include: []string{},
|
||||
|
|
@ -83,7 +91,7 @@ var CommsTextLogs = Table[
|
|||
},
|
||||
PrimaryKey: &constraint{
|
||||
Name: "text_log_pkey",
|
||||
Columns: []string{"destination", "source", "type"},
|
||||
Columns: []string{"id"},
|
||||
Comment: "",
|
||||
},
|
||||
ForeignKeys: commsTextLogForeignKeys{
|
||||
|
|
@ -111,15 +119,17 @@ var CommsTextLogs = Table[
|
|||
}
|
||||
|
||||
type commsTextLogColumns struct {
|
||||
Content column
|
||||
Created column
|
||||
Destination column
|
||||
ID column
|
||||
Origin column
|
||||
Source column
|
||||
Type column
|
||||
}
|
||||
|
||||
func (c commsTextLogColumns) AsSlice() []column {
|
||||
return []column{
|
||||
c.Created, c.Destination, c.Source, c.Type,
|
||||
c.Content, c.Created, c.Destination, c.ID, c.Origin, c.Source,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,23 @@ var Organizations = Table[
|
|||
Where: "",
|
||||
Include: []string{},
|
||||
},
|
||||
OrganizationSlugKey: index{
|
||||
Type: "btree",
|
||||
Name: "organization_slug_key",
|
||||
Columns: []indexColumn{
|
||||
{
|
||||
Name: "slug",
|
||||
Desc: null.FromCond(false, true),
|
||||
IsExpression: false,
|
||||
},
|
||||
},
|
||||
Unique: true,
|
||||
Comment: "",
|
||||
NullsFirst: []bool{false},
|
||||
NullsDistinct: false,
|
||||
Where: "",
|
||||
Include: []string{},
|
||||
},
|
||||
OrganizationWebsiteKey: index{
|
||||
Type: "btree",
|
||||
Name: "organization_website_key",
|
||||
|
|
@ -172,6 +189,11 @@ var Organizations = Table[
|
|||
Columns: []string{"import_district_gid"},
|
||||
Comment: "",
|
||||
},
|
||||
OrganizationSlugKey: constraint{
|
||||
Name: "organization_slug_key",
|
||||
Columns: []string{"slug"},
|
||||
Comment: "",
|
||||
},
|
||||
OrganizationWebsiteKey: constraint{
|
||||
Name: "organization_website_key",
|
||||
Columns: []string{"website"},
|
||||
|
|
@ -203,12 +225,13 @@ func (c organizationColumns) AsSlice() []column {
|
|||
type organizationIndexes struct {
|
||||
OrganizationPkey index
|
||||
OrganizationImportDistrictGidKey index
|
||||
OrganizationSlugKey index
|
||||
OrganizationWebsiteKey index
|
||||
}
|
||||
|
||||
func (i organizationIndexes) AsSlice() []index {
|
||||
return []index{
|
||||
i.OrganizationPkey, i.OrganizationImportDistrictGidKey, i.OrganizationWebsiteKey,
|
||||
i.OrganizationPkey, i.OrganizationImportDistrictGidKey, i.OrganizationSlugKey, i.OrganizationWebsiteKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,12 +247,13 @@ func (f organizationForeignKeys) AsSlice() []foreignKey {
|
|||
|
||||
type organizationUniques struct {
|
||||
OrganizationImportDistrictGidKey constraint
|
||||
OrganizationSlugKey constraint
|
||||
OrganizationWebsiteKey constraint
|
||||
}
|
||||
|
||||
func (u organizationUniques) AsSlice() []constraint {
|
||||
return []constraint{
|
||||
u.OrganizationImportDistrictGidKey, u.OrganizationWebsiteKey,
|
||||
u.OrganizationImportDistrictGidKey, u.OrganizationSlugKey, u.OrganizationWebsiteKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -272,35 +272,32 @@ func (e *CommsMessagetypeemail) Scan(value any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Enum values for CommsMessagetypetext
|
||||
// Enum values for CommsTextorigin
|
||||
const (
|
||||
CommsMessagetypetextInitialContact CommsMessagetypetext = "initial-contact"
|
||||
CommsMessagetypetextReportSubscriptionConfirmation CommsMessagetypetext = "report-subscription-confirmation"
|
||||
CommsMessagetypetextReportStatusScheduled CommsMessagetypetext = "report-status-scheduled"
|
||||
CommsMessagetypetextReportStatusComplete CommsMessagetypetext = "report-status-complete"
|
||||
CommsTextoriginDistrict CommsTextorigin = "district"
|
||||
CommsTextoriginLLM CommsTextorigin = "llm"
|
||||
CommsTextoriginWebsiteAction CommsTextorigin = "website-action"
|
||||
)
|
||||
|
||||
func AllCommsMessagetypetext() []CommsMessagetypetext {
|
||||
return []CommsMessagetypetext{
|
||||
CommsMessagetypetextInitialContact,
|
||||
CommsMessagetypetextReportSubscriptionConfirmation,
|
||||
CommsMessagetypetextReportStatusScheduled,
|
||||
CommsMessagetypetextReportStatusComplete,
|
||||
func AllCommsTextorigin() []CommsTextorigin {
|
||||
return []CommsTextorigin{
|
||||
CommsTextoriginDistrict,
|
||||
CommsTextoriginLLM,
|
||||
CommsTextoriginWebsiteAction,
|
||||
}
|
||||
}
|
||||
|
||||
type CommsMessagetypetext string
|
||||
type CommsTextorigin string
|
||||
|
||||
func (e CommsMessagetypetext) String() string {
|
||||
func (e CommsTextorigin) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e CommsMessagetypetext) Valid() bool {
|
||||
func (e CommsTextorigin) Valid() bool {
|
||||
switch e {
|
||||
case CommsMessagetypetextInitialContact,
|
||||
CommsMessagetypetextReportSubscriptionConfirmation,
|
||||
CommsMessagetypetextReportStatusScheduled,
|
||||
CommsMessagetypetextReportStatusComplete:
|
||||
case CommsTextoriginDistrict,
|
||||
CommsTextoriginLLM,
|
||||
CommsTextoriginWebsiteAction:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
@ -308,44 +305,44 @@ func (e CommsMessagetypetext) Valid() bool {
|
|||
}
|
||||
|
||||
// useful when testing in other packages
|
||||
func (e CommsMessagetypetext) All() []CommsMessagetypetext {
|
||||
return AllCommsMessagetypetext()
|
||||
func (e CommsTextorigin) All() []CommsTextorigin {
|
||||
return AllCommsTextorigin()
|
||||
}
|
||||
|
||||
func (e CommsMessagetypetext) MarshalText() ([]byte, error) {
|
||||
func (e CommsTextorigin) MarshalText() ([]byte, error) {
|
||||
return []byte(e), nil
|
||||
}
|
||||
|
||||
func (e *CommsMessagetypetext) UnmarshalText(text []byte) error {
|
||||
func (e *CommsTextorigin) UnmarshalText(text []byte) error {
|
||||
return e.Scan(text)
|
||||
}
|
||||
|
||||
func (e CommsMessagetypetext) MarshalBinary() ([]byte, error) {
|
||||
func (e CommsTextorigin) MarshalBinary() ([]byte, error) {
|
||||
return []byte(e), nil
|
||||
}
|
||||
|
||||
func (e *CommsMessagetypetext) UnmarshalBinary(data []byte) error {
|
||||
func (e *CommsTextorigin) UnmarshalBinary(data []byte) error {
|
||||
return e.Scan(data)
|
||||
}
|
||||
|
||||
func (e CommsMessagetypetext) Value() (driver.Value, error) {
|
||||
func (e CommsTextorigin) Value() (driver.Value, error) {
|
||||
return string(e), nil
|
||||
}
|
||||
|
||||
func (e *CommsMessagetypetext) Scan(value any) error {
|
||||
func (e *CommsTextorigin) Scan(value any) error {
|
||||
switch x := value.(type) {
|
||||
case string:
|
||||
*e = CommsMessagetypetext(x)
|
||||
*e = CommsTextorigin(x)
|
||||
case []byte:
|
||||
*e = CommsMessagetypetext(x)
|
||||
*e = CommsTextorigin(x)
|
||||
case nil:
|
||||
return fmt.Errorf("cannot nil into CommsMessagetypetext")
|
||||
return fmt.Errorf("cannot nil into CommsTextorigin")
|
||||
default:
|
||||
return fmt.Errorf("cannot scan type %T: %v", value, value)
|
||||
}
|
||||
|
||||
if !e.Valid() {
|
||||
return fmt.Errorf("invalid CommsMessagetypetext value: %s", *e)
|
||||
return fmt.Errorf("invalid CommsTextorigin value: %s", *e)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -324,10 +324,12 @@ func (f *Factory) NewCommsTextLogWithContext(ctx context.Context, mods ...CommsT
|
|||
func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLogTemplate {
|
||||
o := &CommsTextLogTemplate{f: f, alreadyPersisted: true}
|
||||
|
||||
o.Content = func() string { return m.Content }
|
||||
o.Created = func() time.Time { return m.Created }
|
||||
o.Destination = func() string { return m.Destination }
|
||||
o.ID = func() int32 { return m.ID }
|
||||
o.Origin = func() enums.CommsTextorigin { return m.Origin }
|
||||
o.Source = func() string { return m.Source }
|
||||
o.Type = func() enums.CommsMessagetypetext { return m.Type }
|
||||
|
||||
ctx := context.Background()
|
||||
if m.R.DestinationPhone != nil {
|
||||
|
|
|
|||
|
|
@ -101,12 +101,12 @@ func random_enums_CommsMessagetypeemail(f *faker.Faker, limits ...string) enums.
|
|||
return all[f.IntBetween(0, len(all)-1)]
|
||||
}
|
||||
|
||||
func random_enums_CommsMessagetypetext(f *faker.Faker, limits ...string) enums.CommsMessagetypetext {
|
||||
func random_enums_CommsTextorigin(f *faker.Faker, limits ...string) enums.CommsTextorigin {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
var e enums.CommsMessagetypetext
|
||||
var e enums.CommsTextorigin
|
||||
all := e.All()
|
||||
return all[f.IntBetween(0, len(all)-1)]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ func (mods CommsTextLogModSlice) Apply(ctx context.Context, n *CommsTextLogTempl
|
|||
// CommsTextLogTemplate is an object representing the database table.
|
||||
// all columns are optional and should be set by mods
|
||||
type CommsTextLogTemplate struct {
|
||||
Content func() string
|
||||
Created func() time.Time
|
||||
Destination func() string
|
||||
ID func() int32
|
||||
Origin func() enums.CommsTextorigin
|
||||
Source func() string
|
||||
Type func() enums.CommsMessagetypetext
|
||||
|
||||
r commsTextLogR
|
||||
f *Factory
|
||||
|
|
@ -89,6 +91,10 @@ func (t CommsTextLogTemplate) setModelRels(o *models.CommsTextLog) {
|
|||
func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter {
|
||||
m := &models.CommsTextLogSetter{}
|
||||
|
||||
if o.Content != nil {
|
||||
val := o.Content()
|
||||
m.Content = omit.From(val)
|
||||
}
|
||||
if o.Created != nil {
|
||||
val := o.Created()
|
||||
m.Created = omit.From(val)
|
||||
|
|
@ -97,14 +103,18 @@ func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter {
|
|||
val := o.Destination()
|
||||
m.Destination = omit.From(val)
|
||||
}
|
||||
if o.ID != nil {
|
||||
val := o.ID()
|
||||
m.ID = omit.From(val)
|
||||
}
|
||||
if o.Origin != nil {
|
||||
val := o.Origin()
|
||||
m.Origin = omit.From(val)
|
||||
}
|
||||
if o.Source != nil {
|
||||
val := o.Source()
|
||||
m.Source = omit.From(val)
|
||||
}
|
||||
if o.Type != nil {
|
||||
val := o.Type()
|
||||
m.Type = omit.From(val)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
|
@ -127,18 +137,24 @@ func (o CommsTextLogTemplate) BuildManySetter(number int) []*models.CommsTextLog
|
|||
func (o CommsTextLogTemplate) Build() *models.CommsTextLog {
|
||||
m := &models.CommsTextLog{}
|
||||
|
||||
if o.Content != nil {
|
||||
m.Content = o.Content()
|
||||
}
|
||||
if o.Created != nil {
|
||||
m.Created = o.Created()
|
||||
}
|
||||
if o.Destination != nil {
|
||||
m.Destination = o.Destination()
|
||||
}
|
||||
if o.ID != nil {
|
||||
m.ID = o.ID()
|
||||
}
|
||||
if o.Origin != nil {
|
||||
m.Origin = o.Origin()
|
||||
}
|
||||
if o.Source != nil {
|
||||
m.Source = o.Source()
|
||||
}
|
||||
if o.Type != nil {
|
||||
m.Type = o.Type()
|
||||
}
|
||||
|
||||
o.setModelRels(m)
|
||||
|
||||
|
|
@ -159,6 +175,10 @@ func (o CommsTextLogTemplate) BuildMany(number int) models.CommsTextLogSlice {
|
|||
}
|
||||
|
||||
func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) {
|
||||
if !(m.Content.IsValue()) {
|
||||
val := random_string(nil)
|
||||
m.Content = omit.From(val)
|
||||
}
|
||||
if !(m.Created.IsValue()) {
|
||||
val := random_time_Time(nil)
|
||||
m.Created = omit.From(val)
|
||||
|
|
@ -167,14 +187,14 @@ func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) {
|
|||
val := random_string(nil)
|
||||
m.Destination = omit.From(val)
|
||||
}
|
||||
if !(m.Origin.IsValue()) {
|
||||
val := random_enums_CommsTextorigin(nil)
|
||||
m.Origin = omit.From(val)
|
||||
}
|
||||
if !(m.Source.IsValue()) {
|
||||
val := random_string(nil)
|
||||
m.Source = omit.From(val)
|
||||
}
|
||||
if !(m.Type.IsValue()) {
|
||||
val := random_enums_CommsMessagetypetext(nil)
|
||||
m.Type = omit.From(val)
|
||||
}
|
||||
}
|
||||
|
||||
// insertOptRels creates and inserts any optional the relationships on *models.CommsTextLog
|
||||
|
|
@ -312,13 +332,46 @@ type commsTextLogMods struct{}
|
|||
|
||||
func (m commsTextLogMods) RandomizeAllColumns(f *faker.Faker) CommsTextLogMod {
|
||||
return CommsTextLogModSlice{
|
||||
CommsTextLogMods.RandomContent(f),
|
||||
CommsTextLogMods.RandomCreated(f),
|
||||
CommsTextLogMods.RandomDestination(f),
|
||||
CommsTextLogMods.RandomID(f),
|
||||
CommsTextLogMods.RandomOrigin(f),
|
||||
CommsTextLogMods.RandomSource(f),
|
||||
CommsTextLogMods.RandomType(f),
|
||||
}
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m commsTextLogMods) Content(val string) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Content = func() string { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m commsTextLogMods) ContentFunc(f func() string) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Content = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m commsTextLogMods) UnsetContent() CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Content = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m commsTextLogMods) RandomContent(f *faker.Faker) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Content = func() string {
|
||||
return random_string(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m commsTextLogMods) Created(val time.Time) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
|
|
@ -381,6 +434,68 @@ func (m commsTextLogMods) RandomDestination(f *faker.Faker) CommsTextLogMod {
|
|||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m commsTextLogMods) ID(val int32) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.ID = func() int32 { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m commsTextLogMods) IDFunc(f func() int32) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.ID = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m commsTextLogMods) UnsetID() CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.ID = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m commsTextLogMods) RandomID(f *faker.Faker) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.ID = func() int32 {
|
||||
return random_int32(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m commsTextLogMods) Origin(val enums.CommsTextorigin) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Origin = func() enums.CommsTextorigin { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m commsTextLogMods) OriginFunc(f func() enums.CommsTextorigin) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Origin = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m commsTextLogMods) UnsetOrigin() CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Origin = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m commsTextLogMods) RandomOrigin(f *faker.Faker) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Origin = func() enums.CommsTextorigin {
|
||||
return random_enums_CommsTextorigin(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m commsTextLogMods) Source(val string) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
|
|
@ -412,37 +527,6 @@ func (m commsTextLogMods) RandomSource(f *faker.Faker) CommsTextLogMod {
|
|||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m commsTextLogMods) Type(val enums.CommsMessagetypetext) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Type = func() enums.CommsMessagetypetext { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m commsTextLogMods) TypeFunc(f func() enums.CommsMessagetypetext) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Type = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m commsTextLogMods) UnsetType() CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Type = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m commsTextLogMods) RandomType(f *faker.Faker) CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) {
|
||||
o.Type = func() enums.CommsMessagetypetext {
|
||||
return random_enums_CommsMessagetypetext(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (m commsTextLogMods) WithParentsCascading() CommsTextLogMod {
|
||||
return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) {
|
||||
if isDone, _ := commsTextLogWithParentsCascadingCtx.Value(ctx); isDone {
|
||||
|
|
|
|||
34
db/migrations/00041_text_log_overhaul.sql
Normal file
34
db/migrations/00041_text_log_overhaul.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- +goose Up
|
||||
DROP TABLE comms.text_log;
|
||||
DROP TYPE comms.MessageTypeText;
|
||||
CREATE TYPE comms.TextOrigin AS ENUM (
|
||||
'district',
|
||||
'llm',
|
||||
'website-action'
|
||||
);
|
||||
CREATE TABLE comms.text_log (
|
||||
content TEXT NOT NULL,
|
||||
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
destination TEXT NOT NULL REFERENCES comms.phone(e164),
|
||||
id SERIAL,
|
||||
origin comms.TextOrigin NOT NULL,
|
||||
source TEXT NOT NULL REFERENCES comms.phone(e164),
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE comms.text_log;
|
||||
DROP TYPE comms.TextOrigin;
|
||||
CREATE TYPE comms.MessageTypeText AS ENUM (
|
||||
'initial-contact',
|
||||
'report-subscription-confirmation',
|
||||
'report-status-scheduled',
|
||||
'report-status-complete'
|
||||
);
|
||||
CREATE TABLE comms.text_log (
|
||||
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
destination TEXT NOT NULL REFERENCES comms.phone(e164),
|
||||
source TEXT NOT NULL REFERENCES comms.phone(e164),
|
||||
type comms.MessageTypeText NOT NULL,
|
||||
PRIMARY KEY (destination, source, type)
|
||||
);
|
||||
|
|
@ -25,10 +25,12 @@ import (
|
|||
|
||||
// CommsTextLog is an object representing the database table.
|
||||
type CommsTextLog struct {
|
||||
Created time.Time `db:"created" `
|
||||
Destination string `db:"destination,pk" `
|
||||
Source string `db:"source,pk" `
|
||||
Type enums.CommsMessagetypetext `db:"type,pk" `
|
||||
Content string `db:"content" `
|
||||
Created time.Time `db:"created" `
|
||||
Destination string `db:"destination" `
|
||||
ID int32 `db:"id,pk" `
|
||||
Origin enums.CommsTextorigin `db:"origin" `
|
||||
Source string `db:"source" `
|
||||
|
||||
R commsTextLogR `db:"-" `
|
||||
}
|
||||
|
|
@ -52,23 +54,27 @@ type commsTextLogR struct {
|
|||
func buildCommsTextLogColumns(alias string) commsTextLogColumns {
|
||||
return commsTextLogColumns{
|
||||
ColumnsExpr: expr.NewColumnsExpr(
|
||||
"created", "destination", "source", "type",
|
||||
"content", "created", "destination", "id", "origin", "source",
|
||||
).WithParent("comms.text_log"),
|
||||
tableAlias: alias,
|
||||
Content: psql.Quote(alias, "content"),
|
||||
Created: psql.Quote(alias, "created"),
|
||||
Destination: psql.Quote(alias, "destination"),
|
||||
ID: psql.Quote(alias, "id"),
|
||||
Origin: psql.Quote(alias, "origin"),
|
||||
Source: psql.Quote(alias, "source"),
|
||||
Type: psql.Quote(alias, "type"),
|
||||
}
|
||||
}
|
||||
|
||||
type commsTextLogColumns struct {
|
||||
expr.ColumnsExpr
|
||||
tableAlias string
|
||||
Content psql.Expression
|
||||
Created psql.Expression
|
||||
Destination psql.Expression
|
||||
ID psql.Expression
|
||||
Origin psql.Expression
|
||||
Source psql.Expression
|
||||
Type psql.Expression
|
||||
}
|
||||
|
||||
func (c commsTextLogColumns) Alias() string {
|
||||
|
|
@ -83,42 +89,56 @@ func (commsTextLogColumns) AliasedAs(alias string) commsTextLogColumns {
|
|||
// All values are optional, and do not have to be set
|
||||
// Generated columns are not included
|
||||
type CommsTextLogSetter struct {
|
||||
Created omit.Val[time.Time] `db:"created" `
|
||||
Destination omit.Val[string] `db:"destination,pk" `
|
||||
Source omit.Val[string] `db:"source,pk" `
|
||||
Type omit.Val[enums.CommsMessagetypetext] `db:"type,pk" `
|
||||
Content omit.Val[string] `db:"content" `
|
||||
Created omit.Val[time.Time] `db:"created" `
|
||||
Destination omit.Val[string] `db:"destination" `
|
||||
ID omit.Val[int32] `db:"id,pk" `
|
||||
Origin omit.Val[enums.CommsTextorigin] `db:"origin" `
|
||||
Source omit.Val[string] `db:"source" `
|
||||
}
|
||||
|
||||
func (s CommsTextLogSetter) SetColumns() []string {
|
||||
vals := make([]string, 0, 4)
|
||||
vals := make([]string, 0, 6)
|
||||
if s.Content.IsValue() {
|
||||
vals = append(vals, "content")
|
||||
}
|
||||
if s.Created.IsValue() {
|
||||
vals = append(vals, "created")
|
||||
}
|
||||
if s.Destination.IsValue() {
|
||||
vals = append(vals, "destination")
|
||||
}
|
||||
if s.ID.IsValue() {
|
||||
vals = append(vals, "id")
|
||||
}
|
||||
if s.Origin.IsValue() {
|
||||
vals = append(vals, "origin")
|
||||
}
|
||||
if s.Source.IsValue() {
|
||||
vals = append(vals, "source")
|
||||
}
|
||||
if s.Type.IsValue() {
|
||||
vals = append(vals, "type")
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func (s CommsTextLogSetter) Overwrite(t *CommsTextLog) {
|
||||
if s.Content.IsValue() {
|
||||
t.Content = s.Content.MustGet()
|
||||
}
|
||||
if s.Created.IsValue() {
|
||||
t.Created = s.Created.MustGet()
|
||||
}
|
||||
if s.Destination.IsValue() {
|
||||
t.Destination = s.Destination.MustGet()
|
||||
}
|
||||
if s.ID.IsValue() {
|
||||
t.ID = s.ID.MustGet()
|
||||
}
|
||||
if s.Origin.IsValue() {
|
||||
t.Origin = s.Origin.MustGet()
|
||||
}
|
||||
if s.Source.IsValue() {
|
||||
t.Source = s.Source.MustGet()
|
||||
}
|
||||
if s.Type.IsValue() {
|
||||
t.Type = s.Type.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) {
|
||||
|
|
@ -127,31 +147,43 @@ func (s *CommsTextLogSetter) 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, 4)
|
||||
if s.Created.IsValue() {
|
||||
vals[0] = psql.Arg(s.Created.MustGet())
|
||||
vals := make([]bob.Expression, 6)
|
||||
if s.Content.IsValue() {
|
||||
vals[0] = psql.Arg(s.Content.MustGet())
|
||||
} else {
|
||||
vals[0] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Destination.IsValue() {
|
||||
vals[1] = psql.Arg(s.Destination.MustGet())
|
||||
if s.Created.IsValue() {
|
||||
vals[1] = psql.Arg(s.Created.MustGet())
|
||||
} else {
|
||||
vals[1] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Source.IsValue() {
|
||||
vals[2] = psql.Arg(s.Source.MustGet())
|
||||
if s.Destination.IsValue() {
|
||||
vals[2] = psql.Arg(s.Destination.MustGet())
|
||||
} else {
|
||||
vals[2] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Type.IsValue() {
|
||||
vals[3] = psql.Arg(s.Type.MustGet())
|
||||
if s.ID.IsValue() {
|
||||
vals[3] = psql.Arg(s.ID.MustGet())
|
||||
} else {
|
||||
vals[3] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Origin.IsValue() {
|
||||
vals[4] = psql.Arg(s.Origin.MustGet())
|
||||
} else {
|
||||
vals[4] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Source.IsValue() {
|
||||
vals[5] = psql.Arg(s.Source.MustGet())
|
||||
} else {
|
||||
vals[5] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
|
@ -161,7 +193,14 @@ func (s CommsTextLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
|||
}
|
||||
|
||||
func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression {
|
||||
exprs := make([]bob.Expression, 0, 4)
|
||||
exprs := make([]bob.Expression, 0, 6)
|
||||
|
||||
if s.Content.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "content")...),
|
||||
psql.Arg(s.Content),
|
||||
}})
|
||||
}
|
||||
|
||||
if s.Created.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
|
|
@ -177,6 +216,20 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression {
|
|||
}})
|
||||
}
|
||||
|
||||
if s.ID.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "id")...),
|
||||
psql.Arg(s.ID),
|
||||
}})
|
||||
}
|
||||
|
||||
if s.Origin.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "origin")...),
|
||||
psql.Arg(s.Origin),
|
||||
}})
|
||||
}
|
||||
|
||||
if s.Source.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "source")...),
|
||||
|
|
@ -184,41 +237,28 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression {
|
|||
}})
|
||||
}
|
||||
|
||||
if s.Type.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "type")...),
|
||||
psql.Arg(s.Type),
|
||||
}})
|
||||
}
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
// FindCommsTextLog retrieves a single record by primary key
|
||||
// If cols is empty Find will return all columns.
|
||||
func FindCommsTextLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypetext, cols ...string) (*CommsTextLog, error) {
|
||||
func FindCommsTextLog(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*CommsTextLog, error) {
|
||||
if len(cols) == 0 {
|
||||
return CommsTextLogs.Query(
|
||||
sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))),
|
||||
sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))),
|
||||
sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))),
|
||||
sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(IDPK))),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
return CommsTextLogs.Query(
|
||||
sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))),
|
||||
sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))),
|
||||
sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))),
|
||||
sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(IDPK))),
|
||||
sm.Columns(CommsTextLogs.Columns.Only(cols...)),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
// CommsTextLogExists checks the presence of a single record by primary key
|
||||
func CommsTextLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypetext) (bool, error) {
|
||||
func CommsTextLogExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) {
|
||||
return CommsTextLogs.Query(
|
||||
sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))),
|
||||
sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))),
|
||||
sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))),
|
||||
sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(IDPK))),
|
||||
).Exists(ctx, exec)
|
||||
}
|
||||
|
||||
|
|
@ -242,15 +282,11 @@ func (o *CommsTextLog) AfterQueryHook(ctx context.Context, exec bob.Executor, qu
|
|||
|
||||
// primaryKeyVals returns the primary key values of the CommsTextLog
|
||||
func (o *CommsTextLog) primaryKeyVals() bob.Expression {
|
||||
return psql.ArgGroup(
|
||||
o.Destination,
|
||||
o.Source,
|
||||
o.Type,
|
||||
)
|
||||
return psql.Arg(o.ID)
|
||||
}
|
||||
|
||||
func (o *CommsTextLog) pkEQ() dialect.Expression {
|
||||
return psql.Group(psql.Quote("comms.text_log", "destination"), psql.Quote("comms.text_log", "source"), psql.Quote("comms.text_log", "type")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
|
||||
return psql.Quote("comms.text_log", "id").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)
|
||||
}))
|
||||
}
|
||||
|
|
@ -277,9 +313,7 @@ func (o *CommsTextLog) Delete(ctx context.Context, exec bob.Executor) error {
|
|||
// Reload refreshes the CommsTextLog using the executor
|
||||
func (o *CommsTextLog) Reload(ctx context.Context, exec bob.Executor) error {
|
||||
o2, err := CommsTextLogs.Query(
|
||||
sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(o.Destination))),
|
||||
sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(o.Source))),
|
||||
sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(o.Type))),
|
||||
sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(o.ID))),
|
||||
).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -313,7 +347,7 @@ func (o CommsTextLogSlice) pkIN() dialect.Expression {
|
|||
return psql.Raw("NULL")
|
||||
}
|
||||
|
||||
return psql.Group(psql.Quote("comms.text_log", "destination"), psql.Quote("comms.text_log", "source"), psql.Quote("comms.text_log", "type")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
|
||||
return psql.Quote("comms.text_log", "id").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()
|
||||
|
|
@ -328,13 +362,7 @@ func (o CommsTextLogSlice) pkIN() dialect.Expression {
|
|||
func (o CommsTextLogSlice) copyMatchingRows(from ...*CommsTextLog) {
|
||||
for i, old := range o {
|
||||
for _, new := range from {
|
||||
if new.Destination != old.Destination {
|
||||
continue
|
||||
}
|
||||
if new.Source != old.Source {
|
||||
continue
|
||||
}
|
||||
if new.Type != old.Type {
|
||||
if new.ID != old.ID {
|
||||
continue
|
||||
}
|
||||
new.R = old.R
|
||||
|
|
@ -580,10 +608,12 @@ func (commsTextLog0 *CommsTextLog) AttachSourcePhone(ctx context.Context, exec b
|
|||
}
|
||||
|
||||
type commsTextLogWhere[Q psql.Filterable] struct {
|
||||
Content psql.WhereMod[Q, string]
|
||||
Created psql.WhereMod[Q, time.Time]
|
||||
Destination psql.WhereMod[Q, string]
|
||||
ID psql.WhereMod[Q, int32]
|
||||
Origin psql.WhereMod[Q, enums.CommsTextorigin]
|
||||
Source psql.WhereMod[Q, string]
|
||||
Type psql.WhereMod[Q, enums.CommsMessagetypetext]
|
||||
}
|
||||
|
||||
func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] {
|
||||
|
|
@ -592,10 +622,12 @@ func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] {
|
|||
|
||||
func buildCommsTextLogWhere[Q psql.Filterable](cols commsTextLogColumns) commsTextLogWhere[Q] {
|
||||
return commsTextLogWhere[Q]{
|
||||
Content: psql.Where[Q, string](cols.Content),
|
||||
Created: psql.Where[Q, time.Time](cols.Created),
|
||||
Destination: psql.Where[Q, string](cols.Destination),
|
||||
ID: psql.Where[Q, int32](cols.ID),
|
||||
Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin),
|
||||
Source: psql.Where[Q, string](cols.Source),
|
||||
Type: psql.Where[Q, enums.CommsMessagetypetext](cols.Type),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/background"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms"
|
||||
"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"
|
||||
|
|
@ -241,7 +241,7 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", report_id), http.StatusFound)
|
||||
return
|
||||
}
|
||||
phone, err := comms.ParsePhoneNumber(phone_str)
|
||||
phone, err := text.ParsePhoneNumber(phone_str)
|
||||
result, err := psql.Update(
|
||||
um.Table("publicreport.quick"),
|
||||
um.SetCol("reporter_email").ToArg(email),
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="button" onClick="toggleCollapse('collapse-additional-fields')">
|
||||
<button class="btn btn-warning" type="button" onClick="toggleCollapse('collapse-additional-fields')">
|
||||
Answer a few more questions to better help us solve your mosquito problem
|
||||
</button>
|
||||
<div class="collapse" id="collapse-additional-fields">
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ function displaySelectedCoordinates(lngLat) {
|
|||
<input type="hidden" id="map-zoom" name="map-zoom"/>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="button" onClick="toggleCollapse('collapse-additional-fields')">
|
||||
<button class="btn btn-warning" type="button" onClick="toggleCollapse('collapse-additional-fields')">
|
||||
Answer a few more questions to better help us solve your mosquito problem
|
||||
</button>
|
||||
<div class="collapse" id="collapse-additional-fields">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue