tsk-54: Tweak instant message endpoint to save contact and message identification (#56)

Closes #54

Reviewed-on: phoenix/textsender-api#56
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
phoenix
2025-12-22 22:39:29 +00:00
committed by phoenix
parent f5f753fe02
commit 6fa5ede358
10 changed files with 45 additions and 28 deletions
+2 -2
View File
@@ -3,8 +3,8 @@ module git.kundeng.us/phoenix/textsender-api
go 1.25.4 go 1.25.4
require ( require (
git.kundeng.us/phoenix/swoosh v0.0.7 git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556
git.kundeng.us/phoenix/textsender-models v0.0.11 git.kundeng.us/phoenix/textsender-models v0.0.12
github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.0
+4 -4
View File
@@ -1,7 +1,7 @@
git.kundeng.us/phoenix/swoosh v0.0.7 h1:9M42tgKr6o34YOiwds7offvjCUoFzYN2B5TrFKbefFo= git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556 h1:vzTrhx7auc8OlUGmfTDektHXCkoFOKzVhdsW+6oKXMI=
git.kundeng.us/phoenix/swoosh v0.0.7/go.mod h1:9iXlBd7UCMA7/jj8gZ1CxEfBualsClpiJrqGj8Fo2Xw= git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556/go.mod h1:OAh9jVBQ3vRJ1EHTM6pFyWd9eXf1H+CevbDKkJuoDZU=
git.kundeng.us/phoenix/textsender-models v0.0.11 h1:kd2FdeZJhJJAXBm8MoyadtgNGyzC+puU1oR8B8N+MfE= git.kundeng.us/phoenix/textsender-models v0.0.12 h1:ps9H3FS5LyCwQhAiIvg4vYyfLZZ64dex1y9ytb0o9C4=
git.kundeng.us/phoenix/textsender-models v0.0.11/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0= git.kundeng.us/phoenix/textsender-models v0.0.12/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
+1 -1
View File
@@ -5,5 +5,5 @@ import (
) )
type App struct { type App struct {
TwilioConfig *config.TwiloConfig TwilioConfig *config.TwilioConfig
} }
+4 -4
View File
@@ -18,7 +18,7 @@ type Config struct {
ServerPort string ServerPort string
ResetDB bool ResetDB bool
JWTSecret string `env:"JWT_SECRET" required:"true"` JWTSecret string `env:"JWT_SECRET" required:"true"`
TwilioConfig *config.TwiloConfig TwilioConfig *config.TwilioConfig
AllowedOrigins []string AllowedOrigins []string
} }
@@ -49,7 +49,7 @@ func PrintName() {
fmt.Println(version.String()) fmt.Println(version.String())
} }
func Load() (*Config, *config.TwiloConfig, error) { func Load() (*Config, *config.TwilioConfig, error) {
versionFlag := flag.Bool("version", false, "Print version information") versionFlag := flag.Bool("version", false, "Print version information")
resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit") resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit")
port := flag.String("port", PORT, "Server port") port := flag.String("port", PORT, "Server port")
@@ -138,7 +138,7 @@ func unpackAllowedOrigins() []string {
return allowedOriginsSplit return allowedOriginsSplit
} }
func TwilioConfig() (*config.TwiloConfig, error) { func TwilioConfig() (*config.TwilioConfig, error) {
authSid := os.Getenv("TWILIO_AUTH_SID") authSid := os.Getenv("TWILIO_AUTH_SID")
serviceSid := os.Getenv("TWILIO_SERVICE_SID") serviceSid := os.Getenv("TWILIO_SERVICE_SID")
authToken := os.Getenv("TWILIO_AUTH_TOKEN") authToken := os.Getenv("TWILIO_AUTH_TOKEN")
@@ -153,7 +153,7 @@ func TwilioConfig() (*config.TwiloConfig, error) {
} else if len(phoneNumber) == 0 { } else if len(phoneNumber) == 0 {
return nil, fmt.Errorf("Twilio config phone number not found") return nil, fmt.Errorf("Twilio config phone number not found")
} else { } else {
var cfg config.TwiloConfig var cfg config.TwilioConfig
cfg.AccountSID = authSid cfg.AccountSID = authSid
cfg.ServiceSID = serviceSid cfg.ServiceSID = serviceSid
cfg.AuthToken = authToken cfg.AuthToken = authToken
+3 -3
View File
@@ -54,14 +54,14 @@ func (c *ContactHandler) AddContact(w http.ResponseWriter, r *http.Request) {
} }
defer r.Body.Close() defer r.Body.Close()
newContact := contact.Contact{PhoneNumber: req.PhoneNumber, UserId: req.UserId} newContact := contact.Contact{PhoneNumber: req.PhoneNumber, UserId: &req.UserId}
var statusCode int var statusCode int
var resp AddContactResponse var resp AddContactResponse
ctx := r.Context() ctx := r.Context()
if exists, err := c.ContactStore.ContactExists(ctx, newContact.PhoneNumber, newContact.UserId); err != nil { if exists, err := c.ContactStore.ContactExists(ctx, newContact.PhoneNumber, *newContact.UserId); err != nil {
fmt.Printf("Error: %v", err) fmt.Printf("Error: %v", err)
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
@@ -157,7 +157,7 @@ func (c *ContactHandler) GetContact(w http.ResponseWriter, r *http.Request) {
fmt.Println("Checking with User Id") fmt.Println("Checking with User Id")
if contacts, err := c.ContactStore.GetAllContacts(ctx); err == nil { if contacts, err := c.ContactStore.GetAllContacts(ctx); err == nil {
for _, con := range contacts { for _, con := range contacts {
if con.UserId == userId { if *con.UserId == userId {
resp.Data = append(resp.Data, *con) resp.Data = append(resp.Data, *con)
} }
} }
+3
View File
@@ -110,6 +110,9 @@ func (s *SendInstantMessageHandler) Send(w http.ResponseWriter, r *http.Request)
mer.Response = data mer.Response = data
mer.UserId = req.UserId mer.UserId = req.UserId
mer.Sent = parsedTime mer.Sent = parsedTime
mer.Status = message.Message_Event_Response_Status_Instant
mer.ContactId = c.Id
mer.MessageId = &msg.Id
if err := s.MERStore.Create(ctx, &mer); err != nil { if err := s.MERStore.Create(ctx, &mer); err != nil {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
@@ -26,6 +26,9 @@ type RecordEventRequest struct {
Response types.JSONB `json:"response"` Response types.JSONB `json:"response"`
UserId uuid.UUID `json:"user_id"` UserId uuid.UUID `json:"user_id"`
Sent *time.Time `json:"sent,omitempty"` Sent *time.Time `json:"sent,omitempty"`
Status string `json:"status"`
ContactId *uuid.UUID `json:"contact_id"`
MessageId *uuid.UUID `json:"message_id"`
} }
type RecordEventResponse struct { type RecordEventResponse struct {
@@ -62,6 +65,14 @@ func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Req
rp.UserId = req.UserId rp.UserId = req.UserId
rp.Sent = *req.Sent rp.Sent = *req.Sent
if req.Status == message.Message_Event_Response_Status_Instant {
rp.Status = req.Status
rp.MessageId = req.MessageId
rp.ContactId = req.ContactId
} else if req.Status == message.Message_Event_Response_Status_Scheduled {
rp.Status = req.Status
}
ctx := r.Context() ctx := r.Context()
if err := e.MessageEventResponseStore.Create(ctx, &rp); err != nil { if err := e.MessageEventResponseStore.Create(ctx, &rp); err != nil {
resp.Message = err.Error() resp.Message = err.Error()
@@ -60,7 +60,7 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) {
sent := now.Add(30 * time.Minute) sent := now.Add(30 * time.Minute)
bytes := []byte("{\"body\":\"Whoknows?\",\"num_segments\":\"0\",\"direction\":\"outbound-api\",\"from\":\"+12243026041\",\"to\":\"+16303831708\",\"date_updated\":\"Sat,29Nov202519:06:59+0000\",\"uri\":\"/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SM1193a529e7f7a840667cd1e0f13ea95a.json\",\"account_sid\":\"ACefa1ef516314c9d1a68cbd657de49277\",\"num_media\":\"0\",\"status\":\"scheduled\",\"messaging_service_sid\":\"MG803f3676706b92eb02e18dd820c447f2\",\"sid\":\"SM1193a529e7f7a840667cd1e0f13ea95a\",\"date_created\":\"Sat,29Nov202519:06:59+0000\",\"api_version\":\"2010-04-01\",\"subresource_uris\":{\"media\":\"/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SM1193a529e7f7a840667cd1e0f13ea95a/Media.json\"}}") bytes := []byte("{\"body\":\"Whoknows?\",\"num_segments\":\"0\",\"direction\":\"outbound-api\",\"from\":\"+12243026041\",\"to\":\"+16303831708\",\"date_updated\":\"Sat,29Nov202519:06:59+0000\",\"uri\":\"/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SM1193a529e7f7a840667cd1e0f13ea95a.json\",\"account_sid\":\"ACefa1ef516314c9d1a68cbd657de49277\",\"num_media\":\"0\",\"status\":\"scheduled\",\"messaging_service_sid\":\"MG803f3676706b92eb02e18dd820c447f2\",\"sid\":\"SM1193a529e7f7a840667cd1e0f13ea95a\",\"date_created\":\"Sat,29Nov202519:06:59+0000\",\"api_version\":\"2010-04-01\",\"subresource_uris\":{\"media\":\"/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SM1193a529e7f7a840667cd1e0f13ea95a/Media.json\"}}")
testReq := RecordEventRequest{ScheduledMessageEventId: event.Id, Response: bytes, UserId: schMsg.UserId, Sent: &sent} testReq := RecordEventRequest{ScheduledMessageEventId: event.Id, Response: bytes, UserId: schMsg.UserId, Sent: &sent, Status: message.Message_Event_Response_Status_Scheduled}
jsonValue, _ := json.Marshal(testReq) jsonValue, _ := json.Marshal(testReq)
req, _ := http.NewRequest("POST", endpoint.RecordEventResponse, strings.NewReader(string(jsonValue))) req, _ := http.NewRequest("POST", endpoint.RecordEventResponse, strings.NewReader(string(jsonValue)))
@@ -27,10 +27,10 @@ func (m *PGMessageEventResponseStore) Create(ctx context.Context, mer *message.M
queryReturn := "RETURNING id" queryReturn := "RETURNING id"
if mer.ScheduledMessageEventId == uuid.Nil { if mer.ScheduledMessageEventId == uuid.Nil {
query = fmt.Sprintf("%s (response, user_id, sent) VALUES ($1, $2, $3) %s", queryBase, queryReturn) query = fmt.Sprintf("%s (response, user_id, sent, contact_id, message_id) VALUES ($1, $2, $3, $4, $5) %s", queryBase, queryReturn)
return m.db.QueryRow(ctx, query, mer.Response, mer.UserId, mer.Sent).Scan(&mer.Id) return m.db.QueryRow(ctx, query, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId).Scan(&mer.Id)
} else { } else {
query = fmt.Sprintf("%s (scheduled_message_event_id, response, user_id, sent) %s", queryBase, queryReturn) query = fmt.Sprintf("%s (scheduled_message_event_id, response, user_id, sent, contact_id, message_id) VALUES ($1, $2, $3, $4, $5, $6) %s", queryBase, queryReturn)
return m.db.QueryRow(ctx, query, mer.ScheduledMessageEventId, mer.Response, mer.UserId, mer.Sent).Scan(&mer.Id) return m.db.QueryRow(ctx, query, mer.ScheduledMessageEventId, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId).Scan(&mer.Id)
} }
} }
+4 -1
View File
@@ -39,5 +39,8 @@ CREATE TABLE IF NOT EXISTS message_event_responses (
scheduled_message_event_id UUID NULL, scheduled_message_event_id UUID NULL,
response JSONB NOT NULL, response JSONB NOT NULL,
user_id UUID NOT NULL, user_id UUID NOT NULL,
sent timestamptz NOT NULL sent timestamptz NOT NULL,
contact_id UUID NULL,
message_id UUID NULL,
status TEXT CHECK (status IN ('INSTANT', 'SCHEDULED'))
); );