2026-05-15 16:58:28 +00:00
|
|
|
package publicreport
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-05-21 04:26:07 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2026-05-19 15:33:57 +00:00
|
|
|
"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/platform/email"
|
|
|
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/platform/text"
|
|
|
|
|
"source.gleipnir.technology/Gleipnir/nidus-sync/platform/types"
|
2026-05-15 16:58:28 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func DistrictForReport(ctx context.Context, report_id string) (modelpublic.Organization, error) {
|
|
|
|
|
report, err := querypublicreport.ReportFromPublicID(ctx, db.PGInstance.PGXPool, report_id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return modelpublic.Organization{}, fmt.Errorf("Failed to find report %s: %w", report_id, err)
|
|
|
|
|
}
|
|
|
|
|
result, e := querypublic.OrganizationFromID(ctx, db.PGInstance.PGXPool, int64(report.OrganizationID))
|
|
|
|
|
if e != nil {
|
|
|
|
|
return modelpublic.Organization{}, fmt.Errorf("Failed to load organization %d: %w", report.OrganizationID, e)
|
|
|
|
|
}
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RegisterNotificationEmail(ctx context.Context, txn db.Ex, report modelpublicreport.Report, contact modelcomms.Contact, destination string) error {
|
|
|
|
|
err := email.EnsureInDB(ctx, txn, contact, destination)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to ensure phone is in DB: %w", err)
|
|
|
|
|
}
|
|
|
|
|
_, err = querypublicreport.NotifyEmailCreate(ctx, txn, report.ID, destination)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return email.SendReportConfirmation(ctx, txn, contact, destination, report.PublicID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RegisterNotificationPhone(ctx context.Context, txn db.Ex, report modelpublicreport.Report, contact modelcomms.Contact, phone types.E164) error {
|
|
|
|
|
err := text.EnsureInDB(ctx, txn, contact, phone)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to ensure phone is in DB: %w", err)
|
|
|
|
|
}
|
|
|
|
|
_, err = querypublicreport.NotifyPhoneCreate(ctx, txn, report.ID, phone.PhoneString())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return text.ReportSubscriptionConfirmationText(ctx, db.PGInstance.PGXPool, phone, report.PublicID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RegisterSubscriptionEmail(ctx context.Context, txn db.Ex, contact modelcomms.Contact, destination string) error {
|
|
|
|
|
_, err := querypublicreport.SubscribeEmailInsert(ctx, txn, modelpublicreport.SubscribeEmail{
|
|
|
|
|
Created: time.Now(),
|
|
|
|
|
Deleted: nil,
|
|
|
|
|
EmailAddress: destination,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to save new subscription email row: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
func RegisterSubscriptionPhone(ctx context.Context, txn db.Ex, contact modelcomms.Contact, phone types.E164) error {
|
|
|
|
|
_, err := querypublicreport.SubscribePhoneInsert(ctx, txn, modelpublicreport.SubscribePhone{
|
|
|
|
|
Created: time.Now(),
|
|
|
|
|
Deleted: nil,
|
|
|
|
|
PhoneE164: phone.PhoneString(),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to save new subscription phone row: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:59:44 +00:00
|
|
|
func SaveReporter(ctx context.Context, txn db.Ex, report modelpublicreport.Report, name string, email_address string, phone *types.E164, can_sms bool) (contact modelcomms.Contact, err error) {
|
2026-05-21 18:44:28 +00:00
|
|
|
report_contact, err := querycomms.ContactFromID(ctx, txn, int64(report.ReporterContactID))
|
2026-05-21 16:59:44 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return contact, fmt.Errorf("contact query: %w", err)
|
|
|
|
|
}
|
2026-05-21 18:44:28 +00:00
|
|
|
email_contact, err := querycomms.ContactFromEmail(ctx, txn, email_address)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return contact, fmt.Errorf("contact query: %w", err)
|
|
|
|
|
}
|
|
|
|
|
phone_contact, err := querycomms.ContactFromPhone(ctx, txn, phone.PhoneString())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return contact, fmt.Errorf("contact query: %w", err)
|
|
|
|
|
}
|
|
|
|
|
var new_contact_name string
|
|
|
|
|
new_contact_id := report_contact.ID
|
|
|
|
|
if phone_contact != nil {
|
|
|
|
|
// Whether or not the contacts match, we're going to pick the phone contact
|
|
|
|
|
// as the authoritative one, for no particular reason.
|
|
|
|
|
new_contact_id = phone_contact.ID
|
|
|
|
|
if phone_contact.Name != "" {
|
|
|
|
|
new_contact_name = phone_contact.Name
|
|
|
|
|
}
|
|
|
|
|
if email_contact != nil && email_contact.ID != phone_contact.ID {
|
|
|
|
|
err = querycomms.ContactEmailUpdateContactID(ctx, txn, email_address, int64(phone_contact.ID))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return contact, fmt.Errorf("update email contact %d (%d) to %d: %w", email_contact.ID, email_contact.ID, phone_contact.ID, err)
|
|
|
|
|
}
|
|
|
|
|
log.Info().Str("address", email_address).Str("e164", phone.PhoneString()).Int32("from-id", email_contact.ID).Int32("to-id", phone_contact.ID).Msg("merged email contact with phone contact")
|
|
|
|
|
}
|
|
|
|
|
} else if email_contact != nil {
|
|
|
|
|
new_contact_id = email_contact.ID
|
|
|
|
|
if email_contact.Name != "" {
|
|
|
|
|
new_contact_name = email_contact.Name
|
2026-05-15 16:58:28 +00:00
|
|
|
}
|
2026-05-21 16:59:44 +00:00
|
|
|
}
|
2026-05-21 18:44:28 +00:00
|
|
|
if name != "" {
|
|
|
|
|
new_contact_name = name
|
|
|
|
|
}
|
|
|
|
|
if new_contact_id != 0 && new_contact_id != report_contact.ID {
|
|
|
|
|
err = querypublicreport.ReportUpdateReporterContactID(ctx, txn, int64(report.ID), int64(new_contact_id))
|
2026-05-15 16:58:28 +00:00
|
|
|
if err != nil {
|
2026-05-21 18:44:28 +00:00
|
|
|
return contact, fmt.Errorf("update report %d reporter contact from %d to %d: %w", report.ID, report.ReporterContactID, new_contact_id, err)
|
2026-05-15 16:58:28 +00:00
|
|
|
}
|
2026-05-21 18:44:28 +00:00
|
|
|
}
|
|
|
|
|
if new_contact_name != "" {
|
|
|
|
|
err = querycomms.ContactUpdateName(ctx, txn, int64(new_contact_id), new_contact_name)
|
2026-05-21 16:59:44 +00:00
|
|
|
if err != nil {
|
2026-05-21 18:44:28 +00:00
|
|
|
return contact, fmt.Errorf("update contact %d to name %s: %w", new_contact_id, new_contact_name, err)
|
2026-05-15 16:58:28 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-21 16:59:44 +00:00
|
|
|
|
2026-05-21 18:44:28 +00:00
|
|
|
log.Debug().Str("name", name).Str("old name", contact.Name).Int32("id", contact.ID).Msg("contact updated")
|
|
|
|
|
|
|
|
|
|
if email_address != "" && email_contact == nil {
|
|
|
|
|
err = saveReporterEmail(ctx, txn, new_contact_id, email_address)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return contact, fmt.Errorf("new contact email for '%s' on %d: %w", email_address, new_contact_id, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if phone != nil && phone_contact == nil {
|
|
|
|
|
err = saveReporterPhone(ctx, txn, new_contact_id, phone, can_sms)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return contact, fmt.Errorf("new contact phone for '%s' on %d: %w", phone.PhoneString(), new_contact_id, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-15 16:58:28 +00:00
|
|
|
return contact, nil
|
|
|
|
|
}
|
2026-05-21 18:44:28 +00:00
|
|
|
func saveReporterEmail(ctx context.Context, txn db.Ex, contact_id int32, email_address string) error {
|
|
|
|
|
e, err := querycomms.ContactEmailInsert(ctx, txn, modelcomms.ContactEmail{
|
|
|
|
|
Address: email_address,
|
|
|
|
|
Confirmed: false,
|
|
|
|
|
ContactID: contact_id,
|
|
|
|
|
IsSubscribed: false,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("contact add email: %w", err)
|
|
|
|
|
}
|
|
|
|
|
log.Info().Str("address", e.Address).Int32("contact_id", contact_id).Msg("Created contact email")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 20:56:22 +00:00
|
|
|
func saveReporterPhone(ctx context.Context, txn db.Ex, contact_id int32, number *types.E164, can_sms bool) error {
|
|
|
|
|
if number == nil {
|
2026-05-21 18:44:28 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2026-05-22 20:56:22 +00:00
|
|
|
_, err := querycomms.PhoneInsertIfNotExists(ctx, txn, modelcomms.Phone{
|
|
|
|
|
E164: number.PhoneString(),
|
2026-05-21 18:44:28 +00:00
|
|
|
CanSms: can_sms,
|
|
|
|
|
ConfirmedMessageID: nil,
|
|
|
|
|
StopMessageID: nil,
|
|
|
|
|
})
|
2026-05-22 20:56:22 +00:00
|
|
|
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,
|
|
|
|
|
})
|
2026-05-21 18:44:28 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("contact add phone: %w", err)
|
|
|
|
|
}
|
|
|
|
|
log.Info().Str("e164", p.E164).Int32("contact_id", contact_id).Msg("Created contact phone")
|
|
|
|
|
return nil
|
|
|
|
|
}
|