@@ -9,3 +9,4 @@ TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U
|
|||||||
TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T
|
TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T
|
||||||
TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1"
|
TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1"
|
||||||
TWILIO_PHONE_NUMBER=+10123456789
|
TWILIO_PHONE_NUMBER=+10123456789
|
||||||
|
ALLOWED_ORIGINS="http://textsender.com"
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U
|
|||||||
TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T
|
TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T
|
||||||
TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1"
|
TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1"
|
||||||
TWILIO_PHONE_NUMBER=+10123456789
|
TWILIO_PHONE_NUMBER=+10123456789
|
||||||
|
ALLOWED_ORIGINS="http://textsender.com"
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ jobs:
|
|||||||
echo "TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T" >> .env
|
echo "TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T" >> .env
|
||||||
echo "TWILIO_AUTH_TOKEN=f4a1f2b0b79ea3735078c2d8ee9684e1" >> .env
|
echo "TWILIO_AUTH_TOKEN=f4a1f2b0b79ea3735078c2d8ee9684e1" >> .env
|
||||||
echo "TWILIO_PHONE_NUMBER=10123456789" >> .env
|
echo "TWILIO_PHONE_NUMBER=10123456789" >> .env
|
||||||
|
echo "ALLOWED_ORIGINS=http://localhost:5173" >> .env
|
||||||
|
|
||||||
echo "Initializing config"
|
echo "Initializing config"
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/cors"
|
||||||
"github.com/swaggo/http-swagger/v2"
|
"github.com/swaggo/http-swagger/v2"
|
||||||
|
|
||||||
_ "git.kundeng.us/phoenix/textsender-api/docs"
|
_ "git.kundeng.us/phoenix/textsender-api/docs"
|
||||||
@@ -92,6 +93,15 @@ func main() {
|
|||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
// Configure CORS
|
||||||
|
router.Use(cors.Handler(cors.Options{
|
||||||
|
AllowedOrigins: cfg.AllowedOrigins,
|
||||||
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
|
||||||
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||||
|
ExposedHeaders: []string{"Link", "X-Total-Count"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 300, // 5 minutes
|
||||||
|
}))
|
||||||
router.Use(middleware.Logger)
|
router.Use(middleware.Logger)
|
||||||
router.Use(middleware.Recoverer)
|
router.Use(middleware.Recoverer)
|
||||||
router.Use(middleware.Timeout(60 * time.Second))
|
router.Use(middleware.Timeout(60 * time.Second))
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ 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/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.7.6
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
|
|||||||
@@ -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=
|
||||||
@@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
|
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8=
|
github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8=
|
||||||
github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo=
|
github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo=
|
||||||
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
|
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
TwilioConfig *config.TwiloConfig
|
TwilioConfig *config.TwilioConfig
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-12
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.kundeng.us/phoenix/textsender-models/tx0/config"
|
"git.kundeng.us/phoenix/textsender-models/tx0/config"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
@@ -13,11 +14,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DBConnString string
|
DBConnString string
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionInfo struct {
|
type ConnectionInfo struct {
|
||||||
@@ -47,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")
|
||||||
@@ -68,11 +70,13 @@ func Load() (*Config, *config.TwiloConfig, error) {
|
|||||||
if cfg, err := TwilioConfig(); err != nil {
|
if cfg, err := TwilioConfig(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else {
|
} else {
|
||||||
|
allowedOrigins := unpackAllowedOrigins()
|
||||||
return &Config{
|
return &Config{
|
||||||
DBConnString: dbConnString,
|
DBConnString: dbConnString,
|
||||||
ServerPort: *port,
|
ServerPort: *port,
|
||||||
ResetDB: *resetDb,
|
ResetDB: *resetDb,
|
||||||
JWTSecret: os.Getenv("JWT_SECRET"),
|
JWTSecret: os.Getenv("JWT_SECRET"),
|
||||||
|
AllowedOrigins: allowedOrigins,
|
||||||
}, cfg, nil
|
}, cfg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +132,13 @@ func UnpackDBConnString() (connInfo ConnectionInfo) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TwilioConfig() (*config.TwiloConfig, error) {
|
func unpackAllowedOrigins() []string {
|
||||||
|
allowedOriginsRaw := os.Getenv("ALLOWED_ORIGINS")
|
||||||
|
allowedOriginsSplit := strings.Split(allowedOriginsRaw, ",")
|
||||||
|
return allowedOriginsSplit
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
@@ -143,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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func TestGetContactWithMock(t *testing.T) {
|
|||||||
mockstore := mock.NewMockContactStore()
|
mockstore := mock.NewMockContactStore()
|
||||||
testUserId := uuid.New()
|
testUserId := uuid.New()
|
||||||
|
|
||||||
testCon := contact.Contact{PhoneNumber: "+12335403383", UserId: testUserId}
|
testCon := contact.Contact{PhoneNumber: "+12335403383", UserId: &testUserId}
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
if err := mockstore.CreateContact(ctx, &testCon); err != nil {
|
if err := mockstore.CreateContact(ctx, &testCon); err != nil {
|
||||||
assert.NoError(t, err, "Error creating contact")
|
assert.NoError(t, err, "Error creating contact")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event.MessageId = msg.Id
|
event.MessageId = msg.Id
|
||||||
event.RecipientId = con.Id
|
event.RecipientId = *con.Id
|
||||||
event.ScheduledMessageId = schMsg.Id
|
event.ScheduledMessageId = schMsg.Id
|
||||||
if err := mockStore.CreateScheduledMessageEvent(ctx, &event); err != nil {
|
if err := mockStore.CreateScheduledMessageEvent(ctx, &event); err != nil {
|
||||||
assert.NoError(t, err, "Error creating scheduled message event: %v", err)
|
assert.NoError(t, err, "Error creating scheduled message event: %v", err)
|
||||||
@@ -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)))
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func TestUpdateScheduledMessageStatusWithMock(t *testing.T) {
|
|||||||
con := testContact(recipientId, testUserId)
|
con := testContact(recipientId, testUserId)
|
||||||
msg := testMessage(messageId, testUserId)
|
msg := testMessage(messageId, testUserId)
|
||||||
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
||||||
event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id)
|
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id)
|
||||||
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func TestGetScheduledMessageEventWithMock(t *testing.T) {
|
|||||||
con := testContact(recipientId, testUserId)
|
con := testContact(recipientId, testUserId)
|
||||||
msg := testMessage(messageId, testUserId)
|
msg := testMessage(messageId, testUserId)
|
||||||
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
||||||
event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id)
|
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id)
|
||||||
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func TestDeleteScheduledMessageEventWithMock(t *testing.T) {
|
|||||||
con := testContact(recipientId, testUserId)
|
con := testContact(recipientId, testUserId)
|
||||||
msg := testMessage(messageId, testUserId)
|
msg := testMessage(messageId, testUserId)
|
||||||
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
||||||
event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id)
|
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id)
|
||||||
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
@@ -185,9 +185,10 @@ func TestDeleteScheduledMessageEventWithMock(t *testing.T) {
|
|||||||
|
|
||||||
func testContact(id uuid.UUID, userId uuid.UUID) contact.Contact {
|
func testContact(id uuid.UUID, userId uuid.UUID) contact.Contact {
|
||||||
if id == uuid.Nil {
|
if id == uuid.Nil {
|
||||||
return contact.Contact{Id: uuid.New(), PhoneNumber: "+10123456789", UserId: userId}
|
id = uuid.New()
|
||||||
|
return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId}
|
||||||
} else {
|
} else {
|
||||||
return contact.Contact{Id: id, PhoneNumber: "+10123456789", UserId: userId}
|
return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func TestFetchScheduledMessageWithMock(t *testing.T) {
|
|||||||
con := testContact(recipientId, testUserId)
|
con := testContact(recipientId, testUserId)
|
||||||
msg := testMessage(messageId, testUserId)
|
msg := testMessage(messageId, testUserId)
|
||||||
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now)
|
||||||
event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id)
|
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id)
|
||||||
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.kundeng.us/phoenix/textsender-models/tx0/contact"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
PhoneNumber string
|
||||||
|
UserId uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockContactStore struct {
|
||||||
|
Contacts map[uuid.UUID]*contact.Contact
|
||||||
|
ContactsByKey map[Key]*contact.Contact
|
||||||
|
mu sync.RWMutex
|
||||||
|
Error error // Optional: simulate errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockContactStore() *MockContactStore {
|
||||||
|
return &MockContactStore{
|
||||||
|
Contacts: make(map[uuid.UUID]*contact.Contact),
|
||||||
|
ContactsByKey: make(map[Key]*contact.Contact),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockContactStore) CreateContact(ctx context.Context, con *contact.Contact) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var id uuid.UUID
|
||||||
|
if con.Id == nil {
|
||||||
|
id = uuid.New()
|
||||||
|
con.Id = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
key := Key{PhoneNumber: con.PhoneNumber, UserId: *con.UserId}
|
||||||
|
if _, exists := m.ContactsByKey[key]; exists {
|
||||||
|
return errors.New("Contact with phone number already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Contacts[*con.Id] = con
|
||||||
|
m.ContactsByKey[key] = con
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockContactStore) GetContactByID(ctx context.Context, id uuid.UUID) (*contact.Contact, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return nil, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return nil, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
con, exists := m.Contacts[id]
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.New("Contact not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return con, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockContactStore) GetContactByPhone(ctx context.Context, phoneNumber string, userId uuid.UUID) (*contact.Contact, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return nil, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
con, exists := m.ContactsByKey[Key{PhoneNumber: phoneNumber, UserId: userId}]
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.New("Contact not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return con, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockContactStore) GetAllContacts(ctx context.Context) ([]*contact.Contact, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return nil, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
cons := make([]*contact.Contact, 0, len(m.Contacts))
|
||||||
|
for _, con := range m.Contacts {
|
||||||
|
cons = append(cons, con)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cons, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockContactStore) ContactExists(ctx context.Context, phoneNumber string, userId uuid.UUID) (bool, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return false, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := m.ContactsByKey[Key{PhoneNumber: phoneNumber, UserId: userId}]
|
||||||
|
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
@@ -5,117 +5,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.kundeng.us/phoenix/textsender-models/tx0/contact"
|
|
||||||
"git.kundeng.us/phoenix/textsender-models/tx0/message"
|
"git.kundeng.us/phoenix/textsender-models/tx0/message"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Key struct {
|
|
||||||
PhoneNumber string
|
|
||||||
UserId uuid.UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockContactStore struct {
|
|
||||||
Contacts map[uuid.UUID]*contact.Contact
|
|
||||||
ContactsByKey map[Key]*contact.Contact
|
|
||||||
mu sync.RWMutex
|
|
||||||
Error error // Optional: simulate errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMockContactStore() *MockContactStore {
|
|
||||||
return &MockContactStore{
|
|
||||||
Contacts: make(map[uuid.UUID]*contact.Contact),
|
|
||||||
ContactsByKey: make(map[Key]*contact.Contact),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockContactStore) CreateContact(ctx context.Context, con *contact.Contact) error {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.Error != nil {
|
|
||||||
return m.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if con.Id == uuid.Nil {
|
|
||||||
con.Id = uuid.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
key := Key{PhoneNumber: con.PhoneNumber, UserId: con.UserId}
|
|
||||||
if _, exists := m.ContactsByKey[key]; exists {
|
|
||||||
return errors.New("Contact with phone number already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Contacts[con.Id] = con
|
|
||||||
m.ContactsByKey[key] = con
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockContactStore) GetContactByID(ctx context.Context, id uuid.UUID) (*contact.Contact, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.Error != nil {
|
|
||||||
return nil, m.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Error != nil {
|
|
||||||
return nil, m.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
con, exists := m.Contacts[id]
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.New("Contact not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return con, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockContactStore) GetContactByPhone(ctx context.Context, phoneNumber string, userId uuid.UUID) (*contact.Contact, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.Error != nil {
|
|
||||||
return nil, m.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
con, exists := m.ContactsByKey[Key{PhoneNumber: phoneNumber, UserId: userId}]
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.New("Contact not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return con, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockContactStore) GetAllContacts(ctx context.Context) ([]*contact.Contact, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.Error != nil {
|
|
||||||
return nil, m.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
cons := make([]*contact.Contact, 0, len(m.Contacts))
|
|
||||||
for _, con := range m.Contacts {
|
|
||||||
cons = append(cons, con)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cons, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockContactStore) ContactExists(ctx context.Context, phoneNumber string, userId uuid.UUID) (bool, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.Error != nil {
|
|
||||||
return false, m.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
_, exists := m.ContactsByKey[Key{PhoneNumber: phoneNumber, UserId: userId}]
|
|
||||||
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageKey struct {
|
type MessageKey struct {
|
||||||
Content string
|
Content string
|
||||||
UserId uuid.UUID
|
UserId uuid.UUID
|
||||||
|
|||||||
@@ -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'))
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user