Initial creation of endpoint to send messages to public reporters
This commit is contained in:
parent
9707e8793b
commit
cc95c38ab5
12 changed files with 240 additions and 20 deletions
|
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
|
|
@ -39,3 +40,21 @@ func postPublicreportInvalid(ctx context.Context, r *http.Request, user platform
|
|||
URI: config.MakeURLNidus("/publicreport/%s", req.ReportID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type formPublicreportMessage struct {
|
||||
Message string `json:"message"`
|
||||
ReportID string `json:"reportID"`
|
||||
}
|
||||
type createdMessage struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
func postPublicreportMessage(ctx context.Context, r *http.Request, user platform.User, req formPublicreportMessage) (*createdMessage, *nhttp.ErrorWithStatus) {
|
||||
msg_id, err := platform.PublicReportMessageCreate(ctx, user, req.ReportID, req.Message)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("failed to create message: %s", err)
|
||||
}
|
||||
return &createdMessage{
|
||||
URI: config.MakeURLNidus("/message/%s", strconv.Itoa(int(*msg_id))),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ func AddRoutes(r chi.Router) {
|
|||
r.Method("GET", "/mosquito-source", auth.NewEnsureAuth(apiMosquitoSource))
|
||||
r.Method("POST", "/publicreport/invalid", authenticatedHandlerJSONPost(postPublicreportInvalid))
|
||||
r.Method("POST", "/publicreport/lead", authenticatedHandlerJSONPost(postPublicreportLead))
|
||||
r.Method("POST", "/publicreport/message", authenticatedHandlerJSONPost(postPublicreportMessage))
|
||||
r.Method("POST", "/review/pool", authenticatedHandlerJSONPost(postReviewPool))
|
||||
r.Method("GET", "/review-task/pool", authenticatedHandlerJSON(listReviewTaskPool))
|
||||
r.Method("GET", "/service-request", auth.NewEnsureAuth(apiServiceRequest))
|
||||
|
|
|
|||
|
|
@ -103,6 +103,24 @@ var PublicreportReportLocations = Table[
|
|||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
ReporterEmail: column{
|
||||
Name: "reporter_email",
|
||||
DBType: "text",
|
||||
Default: "NULL",
|
||||
Comment: "",
|
||||
Nullable: true,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
ReporterPhone: column{
|
||||
Name: "reporter_phone",
|
||||
DBType: "text",
|
||||
Default: "NULL",
|
||||
Comment: "",
|
||||
Nullable: true,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Status: column{
|
||||
Name: "status",
|
||||
DBType: "publicreport.reportstatustype",
|
||||
|
|
@ -128,12 +146,14 @@ type publicreportReportLocationColumns struct {
|
|||
LocationLongitude column
|
||||
OrganizationID column
|
||||
PublicID column
|
||||
ReporterEmail column
|
||||
ReporterPhone column
|
||||
Status column
|
||||
}
|
||||
|
||||
func (c publicreportReportLocationColumns) AsSlice() []column {
|
||||
return []column{
|
||||
c.ID, c.TableName, c.AddressID, c.AddressRaw, c.Created, c.Location, c.LocationLatitude, c.LocationLongitude, c.OrganizationID, c.PublicID, c.Status,
|
||||
c.ID, c.TableName, c.AddressID, c.AddressRaw, c.Created, c.Location, c.LocationLatitude, c.LocationLongitude, c.OrganizationID, c.PublicID, c.ReporterEmail, c.ReporterPhone, c.Status,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
90
db/migrations/00109_publicreport_reporter_contact.sql
Normal file
90
db/migrations/00109_publicreport_reporter_contact.sql
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
-- +goose Up
|
||||
DROP VIEW publicreport.report_location;
|
||||
CREATE VIEW publicreport.report_location AS
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY table_name, public_id) AS id,
|
||||
table_name,
|
||||
address_id,
|
||||
address_raw,
|
||||
created,
|
||||
location,
|
||||
location_latitude,
|
||||
location_longitude,
|
||||
organization_id,
|
||||
public_id,
|
||||
reporter_email,
|
||||
reporter_phone,
|
||||
status
|
||||
FROM (
|
||||
SELECT
|
||||
'nuisance' AS table_name,
|
||||
address_id,
|
||||
address_raw,
|
||||
created,
|
||||
location,
|
||||
ST_X(location) AS location_longitude,
|
||||
ST_Y(location) AS location_latitude,
|
||||
organization_id,
|
||||
public_id,
|
||||
reporter_email,
|
||||
reporter_phone,
|
||||
status
|
||||
FROM publicreport.nuisance
|
||||
UNION
|
||||
SELECT
|
||||
'water' AS table_name,
|
||||
address_id,
|
||||
address_raw,
|
||||
created,
|
||||
location,
|
||||
ST_X(location) AS location_longitude,
|
||||
ST_Y(location) AS location_latitude,
|
||||
organization_id,
|
||||
public_id,
|
||||
reporter_email,
|
||||
reporter_phone,
|
||||
status
|
||||
FROM publicreport.water
|
||||
) AS combined_data;
|
||||
-- +goose Down
|
||||
DROP VIEW publicreport.report_location;
|
||||
CREATE VIEW publicreport.report_location AS
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY table_name, public_id) AS id,
|
||||
table_name,
|
||||
address_id,
|
||||
address_raw,
|
||||
created,
|
||||
location,
|
||||
location_latitude,
|
||||
location_longitude,
|
||||
organization_id,
|
||||
public_id,
|
||||
status
|
||||
FROM (
|
||||
SELECT
|
||||
'nuisance' AS table_name,
|
||||
address_id,
|
||||
address_raw,
|
||||
created,
|
||||
location,
|
||||
ST_X(location) AS location_longitude,
|
||||
ST_Y(location) AS location_latitude,
|
||||
organization_id,
|
||||
public_id,
|
||||
status
|
||||
FROM publicreport.nuisance
|
||||
UNION
|
||||
SELECT
|
||||
'water' AS table_name,
|
||||
address_id,
|
||||
address_raw,
|
||||
created,
|
||||
location,
|
||||
ST_X(location) AS location_longitude,
|
||||
ST_Y(location) AS location_latitude,
|
||||
organization_id,
|
||||
public_id,
|
||||
status
|
||||
FROM publicreport.water
|
||||
) AS combined_data;
|
||||
|
|
@ -26,6 +26,8 @@ type PublicreportReportLocation struct {
|
|||
LocationLongitude null.Val[float64] `db:"location_longitude" `
|
||||
OrganizationID null.Val[int32] `db:"organization_id" `
|
||||
PublicID null.Val[string] `db:"public_id" `
|
||||
ReporterEmail null.Val[string] `db:"reporter_email" `
|
||||
ReporterPhone null.Val[string] `db:"reporter_phone" `
|
||||
Status null.Val[enums.PublicreportReportstatustype] `db:"status" `
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +44,7 @@ type PublicreportReportLocationsQuery = *psql.ViewQuery[*PublicreportReportLocat
|
|||
func buildPublicreportReportLocationColumns(alias string) publicreportReportLocationColumns {
|
||||
return publicreportReportLocationColumns{
|
||||
ColumnsExpr: expr.NewColumnsExpr(
|
||||
"id", "table_name", "address_id", "address_raw", "created", "location", "location_latitude", "location_longitude", "organization_id", "public_id", "status",
|
||||
"id", "table_name", "address_id", "address_raw", "created", "location", "location_latitude", "location_longitude", "organization_id", "public_id", "reporter_email", "reporter_phone", "status",
|
||||
).WithParent("publicreport.report_location"),
|
||||
tableAlias: alias,
|
||||
ID: psql.Quote(alias, "id"),
|
||||
|
|
@ -55,6 +57,8 @@ func buildPublicreportReportLocationColumns(alias string) publicreportReportLoca
|
|||
LocationLongitude: psql.Quote(alias, "location_longitude"),
|
||||
OrganizationID: psql.Quote(alias, "organization_id"),
|
||||
PublicID: psql.Quote(alias, "public_id"),
|
||||
ReporterEmail: psql.Quote(alias, "reporter_email"),
|
||||
ReporterPhone: psql.Quote(alias, "reporter_phone"),
|
||||
Status: psql.Quote(alias, "status"),
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +76,8 @@ type publicreportReportLocationColumns struct {
|
|||
LocationLongitude psql.Expression
|
||||
OrganizationID psql.Expression
|
||||
PublicID psql.Expression
|
||||
ReporterEmail psql.Expression
|
||||
ReporterPhone psql.Expression
|
||||
Status psql.Expression
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +124,8 @@ type publicreportReportLocationWhere[Q psql.Filterable] struct {
|
|||
LocationLongitude psql.WhereNullMod[Q, float64]
|
||||
OrganizationID psql.WhereNullMod[Q, int32]
|
||||
PublicID psql.WhereNullMod[Q, string]
|
||||
ReporterEmail psql.WhereNullMod[Q, string]
|
||||
ReporterPhone psql.WhereNullMod[Q, string]
|
||||
Status psql.WhereNullMod[Q, enums.PublicreportReportstatustype]
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +145,8 @@ func buildPublicreportReportLocationWhere[Q psql.Filterable](cols publicreportRe
|
|||
LocationLongitude: psql.WhereNull[Q, float64](cols.LocationLongitude),
|
||||
OrganizationID: psql.WhereNull[Q, int32](cols.OrganizationID),
|
||||
PublicID: psql.WhereNull[Q, string](cols.PublicID),
|
||||
ReporterEmail: psql.WhereNull[Q, string](cols.ReporterEmail),
|
||||
ReporterPhone: psql.WhereNull[Q, string](cols.ReporterPhone),
|
||||
Status: psql.WhereNull[Q, enums.PublicreportReportstatustype](cols.Status),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,15 +216,26 @@
|
|||
}
|
||||
},
|
||||
|
||||
sendMessage() {
|
||||
async sendMessage() {
|
||||
if (!this.messageText.trim()) return;
|
||||
|
||||
// TODO: Implement API call to send message
|
||||
console.log(
|
||||
"Sending message to:",
|
||||
this.selectedCommunication.public_report.reporter.has_email,
|
||||
);
|
||||
console.log("Message:", this.messageText);
|
||||
console.log("Sending message reporter:", this.messageText);
|
||||
|
||||
const payload = {
|
||||
message: this.messageText,
|
||||
reportID: this.selectedCommunication.id,
|
||||
};
|
||||
const response = await fetch(`${this.apiBase}/publicreport/message`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Add to activity log
|
||||
if (!this.selectedCommunication.history) {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ import (
|
|||
|
||||
var channelJobText chan text.Job
|
||||
|
||||
func ReportUserText(destination text.E164, report_id string, message string) {
|
||||
//enqueueJobText(text.N
|
||||
}
|
||||
func ReportSubscriptionConfirmationText(destination text.E164, report_id string) {
|
||||
enqueueJobText(text.NewJobReportSubscriptionConfirmation(
|
||||
destination,
|
||||
report_id,
|
||||
config.PhoneNumberReport,
|
||||
*text.NewE164(&config.PhoneNumberReport),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
|||
10
platform/email/report.go
Normal file
10
platform/email/report.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
//"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
)
|
||||
|
||||
func ReportMessage(ctx context.Context, user_id int32, report_id, destination, message string) (*int32, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package platform
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
|
@ -11,20 +12,19 @@ import (
|
|||
"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/Gleipnir-Technology/nidus-sync/platform/background"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/email"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func PublicreportInvalid(ctx context.Context, user User, report_id string) error {
|
||||
location, err := models.PublicreportReportLocations.Query(
|
||||
models.SelectWhere.PublicreportReportLocations.PublicID.EQ(report_id),
|
||||
models.SelectWhere.PublicreportReportLocations.OrganizationID.EQ(user.Organization.ID()),
|
||||
).One(ctx, db.PGInstance.BobDB)
|
||||
tablename, _, err := reportFromID(ctx, user, report_id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query report existence: %w", err)
|
||||
}
|
||||
|
||||
tablename := location.TableName.MustGet()
|
||||
_, err = psql.Update(
|
||||
um.Table("publicreport."+tablename),
|
||||
um.SetCol("reviewed").ToArg(time.Now()),
|
||||
|
|
@ -42,6 +42,27 @@ func PublicreportInvalid(ctx context.Context, user User, report_id string) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func PublicReportMessageCreate(ctx context.Context, user User, report_id, message string) (message_id *int32, err error) {
|
||||
_, report, err := reportFromID(ctx, user, report_id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query report existence: %w", err)
|
||||
}
|
||||
if report.ReporterPhone.GetOr("") != "" {
|
||||
msg_id, err := text.ReportMessage(ctx, int32(user.ID), report_id, report.ReporterPhone.MustGet(), message)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send text: %w", err)
|
||||
}
|
||||
return msg_id, nil
|
||||
} else if report.ReporterEmail.GetOr("") != "" {
|
||||
msg_id, err := email.ReportMessage(ctx, int32(user.ID), report_id, report.ReporterEmail.MustGet(), message)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send email: %w", err)
|
||||
}
|
||||
return msg_id, nil
|
||||
} else {
|
||||
return nil, errors.New("no contact methods available")
|
||||
}
|
||||
}
|
||||
func PublicReportReporterUpdated(ctx context.Context, org_id int32, report_id string, tablename string) {
|
||||
resource := resourceTypeFromTablename(tablename)
|
||||
event.Updated(resource, org_id, report_id)
|
||||
|
|
@ -56,3 +77,14 @@ func resourceTypeFromTablename(tablename string) event.ResourceType {
|
|||
return event.TypeUnknown
|
||||
}
|
||||
}
|
||||
func reportFromID(ctx context.Context, user User, report_id string) (string, *models.PublicreportReportLocation, error) {
|
||||
report, err := models.PublicreportReportLocations.Query(
|
||||
models.SelectWhere.PublicreportReportLocations.PublicID.EQ(report_id),
|
||||
models.SelectWhere.PublicreportReportLocations.OrganizationID.EQ(user.Organization.ID()),
|
||||
).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
tablename := report.TableName.MustGet()
|
||||
return tablename, report, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ 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)
|
||||
return phonenumbers.Format(j.dst.number, phonenumbers.E164)
|
||||
}
|
||||
func (j jobReportSubscription) messageType() MessageType {
|
||||
return ReportSubscription
|
||||
|
|
@ -40,7 +40,7 @@ func (j jobReportSubscription) messageTypeName() string {
|
|||
return "report-subscription"
|
||||
}
|
||||
func (j jobReportSubscription) source() string {
|
||||
return phonenumbers.Format(&j.src, phonenumbers.E164)
|
||||
return phonenumbers.Format(j.src.number, phonenumbers.E164)
|
||||
}
|
||||
|
||||
func sendReportSubscription(ctx context.Context, job Job) error {
|
||||
|
|
|
|||
10
platform/text/report.go
Normal file
10
platform/text/report.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
//"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
)
|
||||
|
||||
func ReportMessage(ctx context.Context, user_id int32, report_id, destination, message string) (*int32, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -23,7 +23,15 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type E164 = phonenumbers.PhoneNumber
|
||||
type E164 struct {
|
||||
number *phonenumbers.PhoneNumber
|
||||
}
|
||||
|
||||
func NewE164(n *phonenumbers.PhoneNumber) *E164 {
|
||||
return &E164{
|
||||
number: n,
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureInDB(ctx context.Context, ex bob.Executor, destination E164) (err error) {
|
||||
return ensureInDB(ctx, ex, PhoneString(destination))
|
||||
|
|
@ -96,11 +104,17 @@ func HandleTextMessage(src string, dst string, body string) {
|
|||
}
|
||||
|
||||
func ParsePhoneNumber(input string) (*E164, error) {
|
||||
return phonenumbers.Parse(input, "US")
|
||||
n, err := phonenumbers.Parse(input, "US")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &E164{
|
||||
number: n,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func PhoneString(p E164) string {
|
||||
return phonenumbers.Format(&p, phonenumbers.E164)
|
||||
return phonenumbers.Format(p.number, phonenumbers.E164)
|
||||
}
|
||||
|
||||
func StoreSources() error {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue