From 37dcc437de9f84c3f1c00e97ef73e232f5ade0bc Mon Sep 17 00:00:00 2001 From: phoenix Date: Thu, 11 Dec 2025 17:37:07 -0500 Subject: [PATCH 01/12] tsk-4: Adding cors package --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 257bfe1..c363d6e 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.11.1 // indirect github.com/swaggo/files/v2 v2.0.2 // indirect github.com/twilio/twilio-go v1.28.8 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect diff --git a/go.sum b/go.sum index 5d25576..bb9434b 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= From fbaa95b2e9a3514ed8049b3f965e453cd9a5d7d0 Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Dec 2025 13:57:55 -0500 Subject: [PATCH 02/12] tsk-4: Added cors package --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c363d6e..a5f8eb7 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.6 github.com/joho/godotenv v1.5.1 + github.com/rs/cors v1.11.1 github.com/stretchr/testify v1.11.1 github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/swag v1.16.6 @@ -35,7 +36,6 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/cors v1.11.1 // indirect github.com/swaggo/files/v2 v2.0.2 // indirect github.com/twilio/twilio-go v1.28.8 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect From fd8fdd93b922d621e158cc2094935a177b02c4e6 Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Dec 2025 13:59:04 -0500 Subject: [PATCH 03/12] tsk-4: Adding CORS support --- cmd/api/main.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index e6cc12a..642012b 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -12,6 +12,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/rs/cors" "github.com/swaggo/http-swagger/v2" _ "git.kundeng.us/phoenix/textsender-api/docs" @@ -115,10 +116,20 @@ func main() { httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.PORT)), )) + // Configure CORS + c := cors.New(cors.Options{ + AllowedOrigins: []string{fmt.Sprintf("http://localhost:%s", config.PORT), "https://textsender.com"}, + 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 + }) + // Start server server := &http.Server{ Addr: ":" + cfg.ServerPort, - Handler: router, + Handler: c.Handler(router), ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, From fa8c7062fcda7f6f5ce79cd6eac4074e5cc8c4f2 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Dec 2025 15:29:39 -0500 Subject: [PATCH 04/12] tsk-4: Switched cors package --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a5f8eb7..2e182ff 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( git.kundeng.us/phoenix/swoosh v0.0.7 git.kundeng.us/phoenix/textsender-models v0.0.11 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/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.6 github.com/joho/godotenv v1.5.1 - github.com/rs/cors v1.11.1 github.com/stretchr/testify v1.11.1 github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/swag v1.16.6 diff --git a/go.sum b/go.sum index bb9434b..17b1333 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= @@ -72,8 +74,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= From 09744d4cdf333182d775d62ecae6b09d93a6778d Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Dec 2025 15:29:54 -0500 Subject: [PATCH 05/12] tsk-4: Fixed code --- cmd/api/main.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 642012b..7972014 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -12,7 +12,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" - "github.com/rs/cors" + "github.com/go-chi/cors" "github.com/swaggo/http-swagger/v2" _ "git.kundeng.us/phoenix/textsender-api/docs" @@ -93,6 +93,15 @@ func main() { router := chi.NewRouter() + // Configure CORS + router.Use( cors.Handler(cors.Options{ + AllowedOrigins: []string{fmt.Sprintf("http://localhost:%s", config.PORT), "http://localhost:5173", "https://textsender.com"}, + 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.Recoverer) router.Use(middleware.Timeout(60 * time.Second)) @@ -116,20 +125,10 @@ func main() { httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.PORT)), )) - // Configure CORS - c := cors.New(cors.Options{ - AllowedOrigins: []string{fmt.Sprintf("http://localhost:%s", config.PORT), "https://textsender.com"}, - 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 - }) - // Start server server := &http.Server{ Addr: ":" + cfg.ServerPort, - Handler: c.Handler(router), + Handler: router, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, From 69b5f4ff8961c894951acf5f7ef086d8ecbb04b8 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Dec 2025 15:30:09 -0500 Subject: [PATCH 06/12] tsk-4: Formatted code --- cmd/api/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 7972014..fcc11a9 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -94,7 +94,7 @@ func main() { router := chi.NewRouter() // Configure CORS - router.Use( cors.Handler(cors.Options{ + router.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{fmt.Sprintf("http://localhost:%s", config.PORT), "http://localhost:5173", "https://textsender.com"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, From f5f753fe02d0faaf29596b19fdfd47d0ee1f5b76 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 21 Dec 2025 00:28:17 +0000 Subject: [PATCH 07/12] tsk-4: Adding CORS support (#53) Closes #4 Reviewed-on: https://git.kundeng.us/phoenix/textsender-api/pulls/53 Co-authored-by: phoenix Co-committed-by: phoenix --- .env.docker.sample | 1 + .env.local.sample | 1 + .gitea/workflows/workflow.yaml | 1 + cmd/api/main.go | 2 +- internal/config/config.go | 10 ++++++++++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.env.docker.sample b/.env.docker.sample index af92142..4bcc074 100644 --- a/.env.docker.sample +++ b/.env.docker.sample @@ -9,3 +9,4 @@ TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1" TWILIO_PHONE_NUMBER=+10123456789 +ALLOWED_ORIGINS="http://textsender.com" diff --git a/.env.local.sample b/.env.local.sample index 5c205fa..e47c1f5 100644 --- a/.env.local.sample +++ b/.env.local.sample @@ -9,3 +9,4 @@ TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1" TWILIO_PHONE_NUMBER=+10123456789 +ALLOWED_ORIGINS="http://textsender.com" diff --git a/.gitea/workflows/workflow.yaml b/.gitea/workflows/workflow.yaml index 1c6034c..dc80dac 100644 --- a/.gitea/workflows/workflow.yaml +++ b/.gitea/workflows/workflow.yaml @@ -107,6 +107,7 @@ jobs: echo "TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T" >> .env echo "TWILIO_AUTH_TOKEN=f4a1f2b0b79ea3735078c2d8ee9684e1" >> .env echo "TWILIO_PHONE_NUMBER=10123456789" >> .env + echo "ALLOWED_ORIGINS=http://localhost:5173" >> .env echo "Initializing config" mkdir -p ~/.ssh diff --git a/cmd/api/main.go b/cmd/api/main.go index fcc11a9..6a7e0a6 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -95,7 +95,7 @@ func main() { // Configure CORS router.Use(cors.Handler(cors.Options{ - AllowedOrigins: []string{fmt.Sprintf("http://localhost:%s", config.PORT), "http://localhost:5173", "https://textsender.com"}, + 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"}, diff --git a/internal/config/config.go b/internal/config/config.go index aabedb2..9efeea0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "strconv" + "strings" "git.kundeng.us/phoenix/textsender-models/tx0/config" "github.com/joho/godotenv" @@ -18,6 +19,7 @@ type Config struct { ResetDB bool JWTSecret string `env:"JWT_SECRET" required:"true"` TwilioConfig *config.TwiloConfig + AllowedOrigins []string } type ConnectionInfo struct { @@ -68,11 +70,13 @@ func Load() (*Config, *config.TwiloConfig, error) { if cfg, err := TwilioConfig(); err != nil { return nil, nil, err } else { + allowedOrigins := unpackAllowedOrigins() return &Config{ DBConnString: dbConnString, ServerPort: *port, ResetDB: *resetDb, JWTSecret: os.Getenv("JWT_SECRET"), + AllowedOrigins: allowedOrigins, }, cfg, nil } } @@ -128,6 +132,12 @@ func UnpackDBConnString() (connInfo ConnectionInfo) { return } +func unpackAllowedOrigins() []string { + allowedOriginsRaw := os.Getenv("ALLOWED_ORIGINS") + allowedOriginsSplit := strings.Split(allowedOriginsRaw, ",") + return allowedOriginsSplit +} + func TwilioConfig() (*config.TwiloConfig, error) { authSid := os.Getenv("TWILIO_AUTH_SID") serviceSid := os.Getenv("TWILIO_SERVICE_SID") From 6fa5ede3587cfc70553313f4dca0e96c080ae13f Mon Sep 17 00:00:00 2001 From: phoenix Date: Mon, 22 Dec 2025 22:39:29 +0000 Subject: [PATCH 08/12] tsk-54: Tweak instant message endpoint to save contact and message identification (#56) Closes #54 Reviewed-on: https://git.kundeng.us/phoenix/textsender-api/pulls/56 Co-authored-by: phoenix Co-committed-by: phoenix --- go.mod | 4 ++-- go.sum | 8 +++---- internal/app/app.go | 2 +- internal/config/config.go | 24 +++++++++---------- internal/handler/contact.go | 6 ++--- internal/handler/instant_message.go | 3 +++ internal/handler/message_event_response.go | 11 +++++++++ .../handler/message_event_response_test.go | 2 +- .../store/message_event_response_store.go | 8 +++---- migrations/schema.sql | 5 +++- 10 files changed, 45 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 2e182ff..51f4ae7 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module git.kundeng.us/phoenix/textsender-api go 1.25.4 require ( - git.kundeng.us/phoenix/swoosh v0.0.7 - git.kundeng.us/phoenix/textsender-models v0.0.11 + git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556 + git.kundeng.us/phoenix/textsender-models v0.0.12 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 diff --git a/go.sum b/go.sum index 17b1333..848543d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -git.kundeng.us/phoenix/swoosh v0.0.7 h1:9M42tgKr6o34YOiwds7offvjCUoFzYN2B5TrFKbefFo= -git.kundeng.us/phoenix/swoosh v0.0.7/go.mod h1:9iXlBd7UCMA7/jj8gZ1CxEfBualsClpiJrqGj8Fo2Xw= -git.kundeng.us/phoenix/textsender-models v0.0.11 h1:kd2FdeZJhJJAXBm8MoyadtgNGyzC+puU1oR8B8N+MfE= -git.kundeng.us/phoenix/textsender-models v0.0.11/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0= +git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556 h1:vzTrhx7auc8OlUGmfTDektHXCkoFOKzVhdsW+6oKXMI= +git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556/go.mod h1:OAh9jVBQ3vRJ1EHTM6pFyWd9eXf1H+CevbDKkJuoDZU= +git.kundeng.us/phoenix/textsender-models v0.0.12 h1:ps9H3FS5LyCwQhAiIvg4vYyfLZZ64dex1y9ytb0o9C4= +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/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= diff --git a/internal/app/app.go b/internal/app/app.go index f09df6f..f2ef963 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,5 +5,5 @@ import ( ) type App struct { - TwilioConfig *config.TwiloConfig + TwilioConfig *config.TwilioConfig } diff --git a/internal/config/config.go b/internal/config/config.go index 9efeea0..f330faa 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,11 +14,11 @@ import ( ) type Config struct { - DBConnString string - ServerPort string - ResetDB bool - JWTSecret string `env:"JWT_SECRET" required:"true"` - TwilioConfig *config.TwiloConfig + DBConnString string + ServerPort string + ResetDB bool + JWTSecret string `env:"JWT_SECRET" required:"true"` + TwilioConfig *config.TwilioConfig AllowedOrigins []string } @@ -49,7 +49,7 @@ func PrintName() { fmt.Println(version.String()) } -func Load() (*Config, *config.TwiloConfig, error) { +func Load() (*Config, *config.TwilioConfig, error) { versionFlag := flag.Bool("version", false, "Print version information") resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit") port := flag.String("port", PORT, "Server port") @@ -72,10 +72,10 @@ func Load() (*Config, *config.TwiloConfig, error) { } else { allowedOrigins := unpackAllowedOrigins() return &Config{ - DBConnString: dbConnString, - ServerPort: *port, - ResetDB: *resetDb, - JWTSecret: os.Getenv("JWT_SECRET"), + DBConnString: dbConnString, + ServerPort: *port, + ResetDB: *resetDb, + JWTSecret: os.Getenv("JWT_SECRET"), AllowedOrigins: allowedOrigins, }, cfg, nil } @@ -138,7 +138,7 @@ func unpackAllowedOrigins() []string { return allowedOriginsSplit } -func TwilioConfig() (*config.TwiloConfig, error) { +func TwilioConfig() (*config.TwilioConfig, error) { authSid := os.Getenv("TWILIO_AUTH_SID") serviceSid := os.Getenv("TWILIO_SERVICE_SID") authToken := os.Getenv("TWILIO_AUTH_TOKEN") @@ -153,7 +153,7 @@ func TwilioConfig() (*config.TwiloConfig, error) { } else if len(phoneNumber) == 0 { return nil, fmt.Errorf("Twilio config phone number not found") } else { - var cfg config.TwiloConfig + var cfg config.TwilioConfig cfg.AccountSID = authSid cfg.ServiceSID = serviceSid cfg.AuthToken = authToken diff --git a/internal/handler/contact.go b/internal/handler/contact.go index 55c6097..bacd14f 100644 --- a/internal/handler/contact.go +++ b/internal/handler/contact.go @@ -54,14 +54,14 @@ func (c *ContactHandler) AddContact(w http.ResponseWriter, r *http.Request) { } 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 resp AddContactResponse 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) statusCode = http.StatusInternalServerError resp.Message = err.Error() @@ -157,7 +157,7 @@ func (c *ContactHandler) GetContact(w http.ResponseWriter, r *http.Request) { fmt.Println("Checking with User Id") if contacts, err := c.ContactStore.GetAllContacts(ctx); err == nil { for _, con := range contacts { - if con.UserId == userId { + if *con.UserId == userId { resp.Data = append(resp.Data, *con) } } diff --git a/internal/handler/instant_message.go b/internal/handler/instant_message.go index deafa46..3160d4b 100644 --- a/internal/handler/instant_message.go +++ b/internal/handler/instant_message.go @@ -110,6 +110,9 @@ func (s *SendInstantMessageHandler) Send(w http.ResponseWriter, r *http.Request) mer.Response = data mer.UserId = req.UserId 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 { statusCode = http.StatusInternalServerError resp.Message = err.Error() diff --git a/internal/handler/message_event_response.go b/internal/handler/message_event_response.go index 8fbe6ec..cd9646b 100644 --- a/internal/handler/message_event_response.go +++ b/internal/handler/message_event_response.go @@ -26,6 +26,9 @@ type RecordEventRequest struct { Response types.JSONB `json:"response"` UserId uuid.UUID `json:"user_id"` 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 { @@ -62,6 +65,14 @@ func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Req rp.UserId = req.UserId 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() if err := e.MessageEventResponseStore.Create(ctx, &rp); err != nil { resp.Message = err.Error() diff --git a/internal/handler/message_event_response_test.go b/internal/handler/message_event_response_test.go index 879ab9f..5420999 100644 --- a/internal/handler/message_event_response_test.go +++ b/internal/handler/message_event_response_test.go @@ -60,7 +60,7 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) { 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\"}}") - 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) req, _ := http.NewRequest("POST", endpoint.RecordEventResponse, strings.NewReader(string(jsonValue))) diff --git a/internal/store/message_event_response_store.go b/internal/store/message_event_response_store.go index 9eae924..031d25e 100644 --- a/internal/store/message_event_response_store.go +++ b/internal/store/message_event_response_store.go @@ -27,10 +27,10 @@ func (m *PGMessageEventResponseStore) Create(ctx context.Context, mer *message.M queryReturn := "RETURNING id" if mer.ScheduledMessageEventId == uuid.Nil { - query = fmt.Sprintf("%s (response, user_id, sent) VALUES ($1, $2, $3) %s", queryBase, queryReturn) - return m.db.QueryRow(ctx, query, mer.Response, mer.UserId, mer.Sent).Scan(&mer.Id) + 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, mer.ContactId, mer.MessageId).Scan(&mer.Id) } else { - query = fmt.Sprintf("%s (scheduled_message_event_id, response, user_id, sent) %s", queryBase, queryReturn) - return m.db.QueryRow(ctx, query, mer.ScheduledMessageEventId, mer.Response, mer.UserId, mer.Sent).Scan(&mer.Id) + 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, mer.ContactId, mer.MessageId).Scan(&mer.Id) } } diff --git a/migrations/schema.sql b/migrations/schema.sql index b37adad..f7a8a1a 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -39,5 +39,8 @@ CREATE TABLE IF NOT EXISTS message_event_responses ( scheduled_message_event_id UUID NULL, response JSONB 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')) ); From 84fed38a673c3d3f2061345409c41497b7b11a36 Mon Sep 17 00:00:00 2001 From: phoenix Date: Mon, 22 Dec 2025 17:47:51 -0500 Subject: [PATCH 09/12] Added mock Contact store --- internal/store/mock/contact_store.go | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 internal/store/mock/contact_store.go diff --git a/internal/store/mock/contact_store.go b/internal/store/mock/contact_store.go new file mode 100644 index 0000000..f99b1b0 --- /dev/null +++ b/internal/store/mock/contact_store.go @@ -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 +} From 0062811cb636410b8d64f1535662eaad88300758 Mon Sep 17 00:00:00 2001 From: phoenix Date: Mon, 22 Dec 2025 17:48:07 -0500 Subject: [PATCH 10/12] Modified mock package --- internal/store/mock/store.go | 106 ----------------------------------- 1 file changed, 106 deletions(-) diff --git a/internal/store/mock/store.go b/internal/store/mock/store.go index d1c571c..f03fce5 100644 --- a/internal/store/mock/store.go +++ b/internal/store/mock/store.go @@ -5,116 +5,10 @@ import ( "errors" "sync" - "git.kundeng.us/phoenix/textsender-models/tx0/contact" "git.kundeng.us/phoenix/textsender-models/tx0/message" "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 { Content string From fb17c02f6925e2643a707ee11aa3092c693120a8 Mon Sep 17 00:00:00 2001 From: phoenix Date: Mon, 22 Dec 2025 17:48:37 -0500 Subject: [PATCH 11/12] Code formatting --- internal/store/mock/store.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/store/mock/store.go b/internal/store/mock/store.go index f03fce5..aa2b014 100644 --- a/internal/store/mock/store.go +++ b/internal/store/mock/store.go @@ -9,7 +9,6 @@ import ( "github.com/google/uuid" ) - type MessageKey struct { Content string UserId uuid.UUID From 14f50a118f179d8a4b9e730998bbc7064009e3b7 Mon Sep 17 00:00:00 2001 From: phoenix Date: Mon, 22 Dec 2025 17:52:45 -0500 Subject: [PATCH 12/12] Test fixes --- internal/handler/contact_test.go | 2 +- internal/handler/message_event_response_test.go | 2 +- internal/handler/schedule_message_status_test.go | 2 +- internal/handler/scheduled_message_event_test.go | 9 +++++---- internal/handler/scheduled_message_test.go | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/handler/contact_test.go b/internal/handler/contact_test.go index 298260e..1ee4388 100644 --- a/internal/handler/contact_test.go +++ b/internal/handler/contact_test.go @@ -54,7 +54,7 @@ func TestGetContactWithMock(t *testing.T) { mockstore := mock.NewMockContactStore() testUserId := uuid.New() - testCon := contact.Contact{PhoneNumber: "+12335403383", UserId: testUserId} + testCon := contact.Contact{PhoneNumber: "+12335403383", UserId: &testUserId} ctx := t.Context() if err := mockstore.CreateContact(ctx, &testCon); err != nil { assert.NoError(t, err, "Error creating contact") diff --git a/internal/handler/message_event_response_test.go b/internal/handler/message_event_response_test.go index 5420999..210099e 100644 --- a/internal/handler/message_event_response_test.go +++ b/internal/handler/message_event_response_test.go @@ -51,7 +51,7 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) { } event.MessageId = msg.Id - event.RecipientId = con.Id + event.RecipientId = *con.Id event.ScheduledMessageId = schMsg.Id if err := mockStore.CreateScheduledMessageEvent(ctx, &event); err != nil { assert.NoError(t, err, "Error creating scheduled message event: %v", err) diff --git a/internal/handler/schedule_message_status_test.go b/internal/handler/schedule_message_status_test.go index 93240ea..10dcbe4 100644 --- a/internal/handler/schedule_message_status_test.go +++ b/internal/handler/schedule_message_status_test.go @@ -32,7 +32,7 @@ func TestUpdateScheduledMessageStatusWithMock(t *testing.T) { con := testContact(recipientId, testUserId) msg := testMessage(messageId, testUserId) schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) - event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id) + event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) ctx := t.Context() diff --git a/internal/handler/scheduled_message_event_test.go b/internal/handler/scheduled_message_event_test.go index 13d6047..56fed9f 100644 --- a/internal/handler/scheduled_message_event_test.go +++ b/internal/handler/scheduled_message_event_test.go @@ -104,7 +104,7 @@ func TestGetScheduledMessageEventWithMock(t *testing.T) { con := testContact(recipientId, testUserId) msg := testMessage(messageId, testUserId) schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) - event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id) + event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) ctx := t.Context() @@ -150,7 +150,7 @@ func TestDeleteScheduledMessageEventWithMock(t *testing.T) { con := testContact(recipientId, testUserId) msg := testMessage(messageId, testUserId) schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) - event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id) + event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) ctx := t.Context() @@ -185,9 +185,10 @@ func TestDeleteScheduledMessageEventWithMock(t *testing.T) { func testContact(id uuid.UUID, userId uuid.UUID) contact.Contact { 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 { - return contact.Contact{Id: id, PhoneNumber: "+10123456789", UserId: userId} + return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId} } } diff --git a/internal/handler/scheduled_message_test.go b/internal/handler/scheduled_message_test.go index 026acb7..b3ca5c3 100644 --- a/internal/handler/scheduled_message_test.go +++ b/internal/handler/scheduled_message_test.go @@ -97,7 +97,7 @@ func TestFetchScheduledMessageWithMock(t *testing.T) { con := testContact(recipientId, testUserId) msg := testMessage(messageId, testUserId) schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) - event := testScheduledMessageEvent(msg.Id, con.Id, schMsg.Id) + event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) ctx := t.Context()