Reviewed-on: phoenix/textsender-api#64
This commit is contained in:
phoenix
2026-01-01 21:04:28 +00:00
29 changed files with 1047 additions and 336 deletions
+1
View File
@@ -119,6 +119,7 @@ func main() {
router.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage))) router.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage)))
router.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage))) router.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage)))
router.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse))) router.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse)))
router.Method("GET", endpoint.FetchMessageEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.Fetch)))
router.Method("POST", endpoint.SendInstantMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(instantMessageHandler.Send))) router.Method("POST", endpoint.SendInstantMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(instantMessageHandler.Send)))
router.Method("GET", "/swagger/*", httpSwagger.Handler( router.Method("GET", "/swagger/*", httpSwagger.Handler(
+10 -8
View File
@@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"flag" "flag"
"fmt"
"log" "log"
"net/http" "net/http"
"os" "os"
@@ -30,8 +29,8 @@ func TestMain(m *testing.M) {
database, err := db.NewDatabase(cfg.GetDBConnString()) database, err := db.NewDatabase(cfg.GetDBConnString())
if err != nil { if err != nil {
fmt.Println(err.Error()) log.Println(err.Error())
panic("Failed to initialize database") log.Fatal("Failed to initialize database")
} }
defer database.Close() defer database.Close()
@@ -39,18 +38,18 @@ func TestMain(m *testing.M) {
if cfg.ResetDB { if cfg.ResetDB {
if err = database.ResetDatabase(ctx); err != nil { if err = database.ResetDatabase(ctx); err != nil {
fmt.Println(err.Error()) log.Println(err.Error())
panic("Failed to initialize database") log.Fatalln("Failed to initialize database")
} }
} else { } else {
if exists, err := db.TableExists(ctx, database.Pool, "contacts "); err == nil && !exists { if exists, err := db.TableExists(ctx, database.Pool, "contacts "); err == nil && !exists {
fmt.Println("Resetting database") log.Println("Resetting database")
err = database.ResetDatabase(ctx) err = database.ResetDatabase(ctx)
if err != nil { if err != nil {
fmt.Printf("Error:%v", err) log.Println("Error:", err)
} }
} else { } else {
fmt.Printf("Error:%v", err) log.Println("Error:", err)
} }
} }
@@ -68,6 +67,7 @@ func TestMain(m *testing.M) {
scheduledMessageEventHandler := handler.NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schStore) scheduledMessageEventHandler := handler.NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schStore)
scheduledMessageStatusHandler := handler.NewScheduledMessageStatusHandler(apiApp, schMsgEventStore, schStore) scheduledMessageStatusHandler := handler.NewScheduledMessageStatusHandler(apiApp, schMsgEventStore, schStore)
eventHandler := handler.NewEventResponseHandler(apiApp, merStore) eventHandler := handler.NewEventResponseHandler(apiApp, merStore)
instantMessageHandler := handler.NewSendInstantMessageHandler(apiApp, merStore, contactStore, messageStore)
testRouter = chi.NewRouter() testRouter = chi.NewRouter()
testRouter.Handle(endpoint.ADD_CONTACT_ENDPOINT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.AddContact))) testRouter.Handle(endpoint.ADD_CONTACT_ENDPOINT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.AddContact)))
@@ -81,6 +81,8 @@ func TestMain(m *testing.M) {
testRouter.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage))) testRouter.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage)))
testRouter.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage))) testRouter.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage)))
testRouter.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse))) testRouter.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse)))
testRouter.Method("GET", endpoint.FetchMessageEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.Fetch)))
testRouter.Method("POST", endpoint.SendInstantMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(instantMessageHandler.Send)))
code := m.Run() code := m.Run()
os.Exit(code) os.Exit(code)
+200 -29
View File
@@ -490,6 +490,106 @@ const docTemplate = `{
} }
} }
}, },
"/schedule/message/event/response": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Fetches a MessageEventResponse given a user id (requires JWT)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"message"
],
"summary": "Fetcht MessageEventResponse",
"parameters": [
{
"type": "string",
"description": "User Id",
"name": "user_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse"
}
}
}
}
},
"/schedule/message/event/response/record": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Saves the result of sending a text message (requires JWT)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"message"
],
"summary": "Record MessageEventResponse",
"parameters": [
{
"description": "Data to add MessageEventResponse",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handler.RecordEventRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/handler.RecordEventResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handler.RecordEventResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handler.RecordEventResponse"
}
}
}
}
},
"/schedule/message/event/{id}": { "/schedule/message/event/{id}": {
"delete": { "delete": {
"security": [ "security": [
@@ -642,9 +742,18 @@ const docTemplate = `{
"contact.Contact": { "contact.Contact": {
"type": "object", "type": "object",
"properties": { "properties": {
"first_name": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_name": {
"type": "string"
},
"nickname": {
"type": "string"
},
"phone_number": { "phone_number": {
"type": "string" "type": "string"
}, },
@@ -653,6 +762,36 @@ const docTemplate = `{
} }
} }
}, },
"event.MessageEventResponse": {
"type": "object",
"properties": {
"contact_id": {
"type": "string"
},
"id": {
"type": "string"
},
"message_id": {
"type": "string"
},
"response": {
"type": "object",
"additionalProperties": {}
},
"scheduled_message_event_id": {
"type": "string"
},
"sent": {
"type": "string"
},
"status": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
},
"handler.AddContactResponse": { "handler.AddContactResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -751,6 +890,20 @@ const docTemplate = `{
} }
} }
}, },
"handler.GetMessageEventResponseFetchedResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/event.MessageEventResponse"
}
},
"message": {
"type": "string"
}
}
},
"handler.GetMessageResponse": { "handler.GetMessageResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -793,6 +946,47 @@ const docTemplate = `{
} }
} }
}, },
"handler.RecordEventRequest": {
"type": "object",
"properties": {
"contact_id": {
"type": "string"
},
"message_id": {
"type": "string"
},
"response": {
"type": "object",
"additionalProperties": {}
},
"scheduled_message_event_id": {
"type": "string"
},
"sent": {
"type": "string"
},
"status": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
},
"handler.RecordEventResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/event.MessageEventResponse"
}
},
"message": {
"type": "string"
}
}
},
"handler.RequestAddContact": { "handler.RequestAddContact": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -832,10 +1026,10 @@ const docTemplate = `{
"handler.RequestAddScheduledMessageEvent": { "handler.RequestAddScheduledMessageEvent": {
"type": "object", "type": "object",
"properties": { "properties": {
"message_id": { "contact_id": {
"type": "string" "type": "string"
}, },
"recipient_id": { "message_id": {
"type": "string" "type": "string"
}, },
"scheduled_message_id": { "scheduled_message_id": {
@@ -902,7 +1096,7 @@ const docTemplate = `{
"data": { "data": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/message.MessageEventResponse" "$ref": "#/definitions/event.MessageEventResponse"
} }
}, },
"message": { "message": {
@@ -924,29 +1118,6 @@ const docTemplate = `{
} }
} }
}, },
"message.MessageEventResponse": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"response": {
"type": "array",
"items": {
"type": "integer"
}
},
"scheduled_message_event_id": {
"type": "string"
},
"sent": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
},
"scheduling.ScheduledMessage": { "scheduling.ScheduledMessage": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -970,6 +1141,9 @@ const docTemplate = `{
"scheduling.ScheduledMessageEvent": { "scheduling.ScheduledMessageEvent": {
"type": "object", "type": "object",
"properties": { "properties": {
"contact_id": {
"type": "string"
},
"created": { "created": {
"type": "string" "type": "string"
}, },
@@ -979,9 +1153,6 @@ const docTemplate = `{
"message_id": { "message_id": {
"type": "string" "type": "string"
}, },
"recipient_id": {
"type": "string"
},
"scheduled_message_id": { "scheduled_message_id": {
"type": "string" "type": "string"
} }
+200 -29
View File
@@ -484,6 +484,106 @@
} }
} }
}, },
"/schedule/message/event/response": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Fetches a MessageEventResponse given a user id (requires JWT)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"message"
],
"summary": "Fetcht MessageEventResponse",
"parameters": [
{
"type": "string",
"description": "User Id",
"name": "user_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse"
}
}
}
}
},
"/schedule/message/event/response/record": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Saves the result of sending a text message (requires JWT)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"message"
],
"summary": "Record MessageEventResponse",
"parameters": [
{
"description": "Data to add MessageEventResponse",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handler.RecordEventRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/handler.RecordEventResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handler.RecordEventResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handler.RecordEventResponse"
}
}
}
}
},
"/schedule/message/event/{id}": { "/schedule/message/event/{id}": {
"delete": { "delete": {
"security": [ "security": [
@@ -636,9 +736,18 @@
"contact.Contact": { "contact.Contact": {
"type": "object", "type": "object",
"properties": { "properties": {
"first_name": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_name": {
"type": "string"
},
"nickname": {
"type": "string"
},
"phone_number": { "phone_number": {
"type": "string" "type": "string"
}, },
@@ -647,6 +756,36 @@
} }
} }
}, },
"event.MessageEventResponse": {
"type": "object",
"properties": {
"contact_id": {
"type": "string"
},
"id": {
"type": "string"
},
"message_id": {
"type": "string"
},
"response": {
"type": "object",
"additionalProperties": {}
},
"scheduled_message_event_id": {
"type": "string"
},
"sent": {
"type": "string"
},
"status": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
},
"handler.AddContactResponse": { "handler.AddContactResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -745,6 +884,20 @@
} }
} }
}, },
"handler.GetMessageEventResponseFetchedResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/event.MessageEventResponse"
}
},
"message": {
"type": "string"
}
}
},
"handler.GetMessageResponse": { "handler.GetMessageResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -787,6 +940,47 @@
} }
} }
}, },
"handler.RecordEventRequest": {
"type": "object",
"properties": {
"contact_id": {
"type": "string"
},
"message_id": {
"type": "string"
},
"response": {
"type": "object",
"additionalProperties": {}
},
"scheduled_message_event_id": {
"type": "string"
},
"sent": {
"type": "string"
},
"status": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
},
"handler.RecordEventResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/event.MessageEventResponse"
}
},
"message": {
"type": "string"
}
}
},
"handler.RequestAddContact": { "handler.RequestAddContact": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -826,10 +1020,10 @@
"handler.RequestAddScheduledMessageEvent": { "handler.RequestAddScheduledMessageEvent": {
"type": "object", "type": "object",
"properties": { "properties": {
"message_id": { "contact_id": {
"type": "string" "type": "string"
}, },
"recipient_id": { "message_id": {
"type": "string" "type": "string"
}, },
"scheduled_message_id": { "scheduled_message_id": {
@@ -896,7 +1090,7 @@
"data": { "data": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/message.MessageEventResponse" "$ref": "#/definitions/event.MessageEventResponse"
} }
}, },
"message": { "message": {
@@ -918,29 +1112,6 @@
} }
} }
}, },
"message.MessageEventResponse": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"response": {
"type": "array",
"items": {
"type": "integer"
}
},
"scheduled_message_event_id": {
"type": "string"
},
"sent": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
},
"scheduling.ScheduledMessage": { "scheduling.ScheduledMessage": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -964,6 +1135,9 @@
"scheduling.ScheduledMessageEvent": { "scheduling.ScheduledMessageEvent": {
"type": "object", "type": "object",
"properties": { "properties": {
"contact_id": {
"type": "string"
},
"created": { "created": {
"type": "string" "type": "string"
}, },
@@ -973,9 +1147,6 @@
"message_id": { "message_id": {
"type": "string" "type": "string"
}, },
"recipient_id": {
"type": "string"
},
"scheduled_message_id": { "scheduled_message_id": {
"type": "string" "type": "string"
} }
+130 -20
View File
@@ -2,13 +2,39 @@ basePath: /api/v1
definitions: definitions:
contact.Contact: contact.Contact:
properties: properties:
first_name:
type: string
id: id:
type: string type: string
last_name:
type: string
nickname:
type: string
phone_number: phone_number:
type: string type: string
user_id: user_id:
type: string type: string
type: object type: object
event.MessageEventResponse:
properties:
contact_id:
type: string
id:
type: string
message_id:
type: string
response:
additionalProperties: {}
type: object
scheduled_message_event_id:
type: string
sent:
type: string
status:
type: string
user_id:
type: string
type: object
handler.AddContactResponse: handler.AddContactResponse:
properties: properties:
data: data:
@@ -72,6 +98,15 @@ definitions:
message: message:
type: string type: string
type: object type: object
handler.GetMessageEventResponseFetchedResponse:
properties:
data:
items:
$ref: '#/definitions/event.MessageEventResponse'
type: array
message:
type: string
type: object
handler.GetMessageResponse: handler.GetMessageResponse:
properties: properties:
data: data:
@@ -99,6 +134,33 @@ definitions:
message: message:
type: string type: string
type: object type: object
handler.RecordEventRequest:
properties:
contact_id:
type: string
message_id:
type: string
response:
additionalProperties: {}
type: object
scheduled_message_event_id:
type: string
sent:
type: string
status:
type: string
user_id:
type: string
type: object
handler.RecordEventResponse:
properties:
data:
items:
$ref: '#/definitions/event.MessageEventResponse'
type: array
message:
type: string
type: object
handler.RequestAddContact: handler.RequestAddContact:
properties: properties:
phone_number: phone_number:
@@ -124,9 +186,9 @@ definitions:
type: object type: object
handler.RequestAddScheduledMessageEvent: handler.RequestAddScheduledMessageEvent:
properties: properties:
message_id: contact_id:
type: string type: string
recipient_id: message_id:
type: string type: string
scheduled_message_id: scheduled_message_id:
type: string type: string
@@ -169,7 +231,7 @@ definitions:
properties: properties:
data: data:
items: items:
$ref: '#/definitions/message.MessageEventResponse' $ref: '#/definitions/event.MessageEventResponse'
type: array type: array
message: message:
type: string type: string
@@ -183,21 +245,6 @@ definitions:
user_id: user_id:
type: string type: string
type: object type: object
message.MessageEventResponse:
properties:
id:
type: string
response:
items:
type: integer
type: array
scheduled_message_event_id:
type: string
sent:
type: string
user_id:
type: string
type: object
scheduling.ScheduledMessage: scheduling.ScheduledMessage:
properties: properties:
created: created:
@@ -213,14 +260,14 @@ definitions:
type: object type: object
scheduling.ScheduledMessageEvent: scheduling.ScheduledMessageEvent:
properties: properties:
contact_id:
type: string
created: created:
type: string type: string
id: id:
type: string type: string
message_id: message_id:
type: string type: string
recipient_id:
type: string
scheduled_message_id: scheduled_message_id:
type: string type: string
type: object type: object
@@ -569,6 +616,69 @@ paths:
summary: Delete scheduled message event summary: Delete scheduled message event
tags: tags:
- scheduled message events - scheduled message events
/schedule/message/event/response:
get:
consumes:
- application/json
description: Fetches a MessageEventResponse given a user id (requires JWT)
parameters:
- description: User Id
in: path
name: user_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.GetMessageEventResponseFetchedResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handler.GetMessageEventResponseFetchedResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handler.GetMessageEventResponseFetchedResponse'
security:
- BearerAuth: []
summary: Fetcht MessageEventResponse
tags:
- message
/schedule/message/event/response/record:
post:
consumes:
- application/json
description: Saves the result of sending a text message (requires JWT)
parameters:
- description: Data to add MessageEventResponse
in: body
name: request
required: true
schema:
$ref: '#/definitions/handler.RecordEventRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/handler.RecordEventResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handler.RecordEventResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handler.RecordEventResponse'
security:
- BearerAuth: []
summary: Record MessageEventResponse
tags:
- message
/schedule/message/fetch: /schedule/message/fetch:
get: get:
consumes: consumes:
+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-8-523c7cf67c-556 git.kundeng.us/phoenix/swoosh v0.1.0
git.kundeng.us/phoenix/textsender-models v0.0.12 git.kundeng.us/phoenix/textsender-models v0.1.6
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-8-523c7cf67c-556 h1:vzTrhx7auc8OlUGmfTDektHXCkoFOKzVhdsW+6oKXMI= git.kundeng.us/phoenix/swoosh v0.1.0 h1:9P+wLuJsIZmVcRoG+W22l06hwp54eNrYbsBtGjucnjI=
git.kundeng.us/phoenix/swoosh v0.0.7-8-523c7cf67c-556/go.mod h1:OAh9jVBQ3vRJ1EHTM6pFyWd9eXf1H+CevbDKkJuoDZU= git.kundeng.us/phoenix/swoosh v0.1.0/go.mod h1:6/97/aS8KVdYqlczQ9ALvS3GVoce1UVl+D8VQK42Bd0=
git.kundeng.us/phoenix/textsender-models v0.0.12 h1:ps9H3FS5LyCwQhAiIvg4vYyfLZZ64dex1y9ytb0o9C4= git.kundeng.us/phoenix/textsender-models v0.1.6 h1:d/rhvqjFSt1Ty/3UfwS21be+lYb9EtWEaDtkzfbzEp0=
git.kundeng.us/phoenix/textsender-models v0.0.12/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0= git.kundeng.us/phoenix/textsender-models v0.1.6/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=
+2 -2
View File
@@ -1,9 +1,9 @@
package app package app
import ( import (
"git.kundeng.us/phoenix/textsender-models/tx0/config" auxcfg "git.kundeng.us/phoenix/textsender-models/tx0/config/auxiliary"
) )
type App struct { type App struct {
TwilioConfig *config.TwilioConfig TwilioConfig *auxcfg.TwilioConfig
} }
+5 -5
View File
@@ -7,7 +7,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"git.kundeng.us/phoenix/textsender-models/tx0/config" auxcfg "git.kundeng.us/phoenix/textsender-models/tx0/config/auxiliary"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"git.kundeng.us/phoenix/textsender-api/internal/version" "git.kundeng.us/phoenix/textsender-api/internal/version"
@@ -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.TwilioConfig TwilioConfig *auxcfg.TwilioConfig
AllowedOrigins []string AllowedOrigins []string
} }
@@ -49,7 +49,7 @@ func PrintName() {
fmt.Println(version.String()) fmt.Println(version.String())
} }
func Load() (*Config, *config.TwilioConfig, error) { func Load() (*Config, *auxcfg.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.TwilioConfig, error) { func TwilioConfig() (*auxcfg.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.TwilioConfig, 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.TwilioConfig var cfg auxcfg.TwilioConfig
cfg.AccountSID = authSid cfg.AccountSID = authSid
cfg.ServiceSID = serviceSid cfg.ServiceSID = serviceSid
cfg.AuthToken = authToken cfg.AuthToken = authToken
-10
View File
@@ -1,7 +1,6 @@
package handler package handler
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -13,7 +12,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"git.kundeng.us/phoenix/textsender-api/internal/db"
"git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint"
"git.kundeng.us/phoenix/textsender-api/internal/store/mock" "git.kundeng.us/phoenix/textsender-api/internal/store/mock"
) )
@@ -80,11 +78,3 @@ func TestGetContactWithMock(t *testing.T) {
assert.NotNil(t, response.Data[0].Id, "Id should not be nil") assert.NotNil(t, response.Data[0].Id, "Id should not be nil")
} }
func resetTestDB(t *testing.T) {
t.Helper()
_, err := db.Pool.Exec(context.Background(), "DELETE FROM contacts")
if err != nil {
t.Fatalf("Failed to reset test database: %v", err)
}
}
+26 -14
View File
@@ -1,16 +1,28 @@
package endpoint package endpoint
const MESSAGE_DRAFT_ENDPOINT = "/api/v1/message/draft" const (
const ADD_MESSAGE = "/api/v1/message/new" MESSAGE_DRAFT_ENDPOINT = "/api/v1/message/draft"
const GET_MESSAGE = "/api/v1/message"
const GET_CONTACT = "/api/v1/contact" ADD_MESSAGE = "/api/v1/message/new"
const ADD_CONTACT_ENDPOINT = "/api/v1/contact/new" GET_MESSAGE = "/api/v1/message"
const ScheduleMessageEndpoint = "/api/v1/schedule/message"
const GetScheduledMessageEndpoint = "/api/v1/schedule/message" GET_CONTACT = "/api/v1/contact"
const AddEventToScheduledMessageEndpoint = "/api/v1/schedule/message/event" ADD_CONTACT_ENDPOINT = "/api/v1/contact/new"
const GetScheduledMessageEventEndpoint = "/api/v1/schedule/message/event"
const DeleteScheduledMessageEventEndpoint = "/api/v1/schedule/message/event/{id}" ScheduleMessageEndpoint = "/api/v1/schedule/message"
const UpdateScheduledMessageStatusEndpoint = "/api/v1/schedule/message/status/update" GetScheduledMessageEndpoint = "/api/v1/schedule/message"
const FetchNextScheduledMessageEndpoint = "/api/v1/schedule/message/fetch"
const RecordEventResponse = "/api/v1/schedule/message/event/response/record" AddEventToScheduledMessageEndpoint = "/api/v1/schedule/message/event"
const SendInstantMessageEndpoint = "/api/v1/instant/message" GetScheduledMessageEventEndpoint = "/api/v1/schedule/message/event"
DeleteScheduledMessageEventEndpoint = "/api/v1/schedule/message/event/{id}"
UpdateScheduledMessageStatusEndpoint = "/api/v1/schedule/message/status/update"
FetchNextScheduledMessageEndpoint = "/api/v1/schedule/message/fetch"
// TODO: Tweak endpoint verbage for MessageEventResponse endpoint
// TODO: Make RecordEventResponse more consistent with MessageEventResponse
RecordEventResponse = "/api/v1/schedule/message/event/response/record"
FetchMessageEventResponse = "/api/v1/schedule/message/event/response"
SendInstantMessageEndpoint = "/api/v1/instant/message"
)
+80
View File
@@ -1,14 +1,23 @@
package handler package handler
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path" "path"
"testing"
"time"
"git.kundeng.us/phoenix/textsender-models/tx0/contact"
"git.kundeng.us/phoenix/textsender-models/tx0/message"
"git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling"
"github.com/google/uuid"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"git.kundeng.us/phoenix/textsender-api/internal/app" "git.kundeng.us/phoenix/textsender-api/internal/app"
"git.kundeng.us/phoenix/textsender-api/internal/config" "git.kundeng.us/phoenix/textsender-api/internal/config"
"git.kundeng.us/phoenix/textsender-api/internal/db"
) )
func GetApp() (*app.App, error) { func GetApp() (*app.App, error) {
@@ -29,3 +38,74 @@ func GetApp() (*app.App, error) {
return &app.App{TwilioConfig: tCfg}, nil return &app.App{TwilioConfig: tCfg}, nil
} }
} }
func ResetTestDB(t *testing.T, tableName string) {
t.Helper()
_, err := db.Pool.Exec(context.Background(), fmt.Sprintf("DELETE FROM %s", tableName))
if err != nil {
t.Fatalf("Failed to reset test database: %v", err)
}
}
func ContactTest(id uuid.UUID, userId uuid.UUID) contact.Contact {
if id == uuid.Nil {
id = uuid.New()
return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId}
} else {
return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId}
}
}
func MessageTest(id uuid.UUID, userId uuid.UUID) message.Message {
if id == uuid.Nil {
id = uuid.New()
return message.Message{Id: &id, Content: "Oh how the mighty have fallen", UserId: &userId}
} else {
return message.Message{Id: &id, Content: "Oh how the mighty have fallen", UserId: &userId}
}
}
func ScheduledMessageTest(id uuid.UUID, userId uuid.UUID, now time.Time) scheduling.ScheduledMessage {
if id == uuid.Nil {
return scheduling.ScheduledMessage{Id: uuid.New(), UserId: userId, Scheduled: now.Add(20 * time.Minute), Status: scheduling.Pending}
} else {
return scheduling.ScheduledMessage{Id: id, UserId: userId, Scheduled: now.Add(20 * time.Minute), Status: scheduling.Pending}
}
}
func ScheduledMessageEventTest(messageId, contactId, scheduledMessageId uuid.UUID) scheduling.ScheduledMessageEvent {
return scheduling.ScheduledMessageEvent{MessageId: messageId, ContactId: contactId, ScheduledMessageId: scheduledMessageId}
}
func MERResponseInString() string {
return `{
"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"
}
}`
}
func ConvertStringToMapOfAny(response string) (map[string]any, error) {
bytes := []byte(response)
var merResponse map[string]any
err := json.Unmarshal(bytes, &merResponse)
if err != nil {
return nil, err
} else {
return merResponse, nil
}
}
+17 -22
View File
@@ -2,7 +2,6 @@ package handler
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
@@ -12,6 +11,7 @@ import (
"git.kundeng.us/phoenix/swoosh/swoop/send" "git.kundeng.us/phoenix/swoosh/swoop/send"
"git.kundeng.us/phoenix/textsender-models/tx0/contact" "git.kundeng.us/phoenix/textsender-models/tx0/contact"
"git.kundeng.us/phoenix/textsender-models/tx0/message" "git.kundeng.us/phoenix/textsender-models/tx0/message"
"git.kundeng.us/phoenix/textsender-models/tx0/message/event"
"github.com/google/uuid" "github.com/google/uuid"
"git.kundeng.us/phoenix/textsender-api/internal/app" "git.kundeng.us/phoenix/textsender-api/internal/app"
@@ -38,8 +38,8 @@ type SendInstantMessageRequest struct {
} }
type SendInstantMessageResponse struct { type SendInstantMessageResponse struct {
Message string `json:"message"` Message string `json:"message"`
Data []*message.MessageEventResponse `json:"data"` Data []*event.MessageEventResponse `json:"data"`
} }
// Sent godoc // Sent godoc
@@ -93,34 +93,29 @@ func (s *SendInstantMessageHandler) Send(w http.ResponseWriter, r *http.Request)
if s.App != nil { if s.App != nil {
msgSender := send.MessageSender{Config: s.App.TwilioConfig} msgSender := send.MessageSender{Config: s.App.TwilioConfig}
for _, c := range contacts { for _, c := range contacts {
if twilioResp, err := msgSender.Send(*msg, *c, nil); err != nil { if twilioResp, twilioRespObject, err := msgSender.Send(*msg, *c, nil); err != nil {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
} else { } else {
if data, err := json.Marshal(twilioResp); err != nil { dateStr := strings.TrimSpace(*twilioResp.DateUpdated)
if parsedTime, err := time.Parse(time.RFC1123Z, dateStr); err != nil {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
} else { } else {
dateStr := strings.TrimSpace(*twilioResp.DateUpdated) mer := event.MessageEventResponse{}
if parsedTime, err := time.Parse(time.RFC1123Z, dateStr); err != nil { mer.Response = twilioRespObject
mer.UserId = req.UserId
mer.Sent = parsedTime
mer.Status = event.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 statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
} else { } else {
mer := message.MessageEventResponse{} statusCode = http.StatusOK
mer.Response = data resp.Message = "Successful"
mer.UserId = req.UserId resp.Data = append(resp.Data, &mer)
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()
} else {
statusCode = http.StatusOK
resp.Message = "Successful"
resp.Data = append(resp.Data, &mer)
}
} }
} }
} }
+12 -17
View File
@@ -12,16 +12,6 @@ import (
"git.kundeng.us/phoenix/textsender-api/internal/store" "git.kundeng.us/phoenix/textsender-api/internal/store"
) )
type RequestAddMessage struct {
Content string `json:"content"`
UserId uuid.UUID `json:"user_id"`
}
type AddMessageResponse struct {
Message string `json:"message"`
Data []message.Message `json:"data"`
}
type MessageHandler struct { type MessageHandler struct {
App *app.App App *app.App
MessageStore store.MessageStore MessageStore store.MessageStore
@@ -33,6 +23,16 @@ func NewMessageHandler(apiApp *app.App, str store.MessageStore) *MessageHandler
const Message_Limit = 200 const Message_Limit = 200
type RequestAddMessage struct {
Content string `json:"content"`
UserId uuid.UUID `json:"user_id"`
}
type AddMessageResponse struct {
Message string `json:"message"`
Data []message.Message `json:"data"`
}
// AddMessage godoc // AddMessage godoc
// @Summary Add message // @Summary Add message
// @Description Add a message record to send a text to (requires JWT) // @Description Add a message record to send a text to (requires JWT)
@@ -46,11 +46,6 @@ const Message_Limit = 200
// @Failure 500 {object} AddMessageResponse // @Failure 500 {object} AddMessageResponse
// @Router /message/new [post] // @Router /message/new [post]
func (m *MessageHandler) AddMessage(w http.ResponseWriter, r *http.Request) { func (m *MessageHandler) AddMessage(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req RequestAddMessage var req RequestAddMessage
if err := ExtractFromRequest(r, &req); err != nil { if err := ExtractFromRequest(r, &req); err != nil {
http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
@@ -59,7 +54,7 @@ func (m *MessageHandler) AddMessage(w http.ResponseWriter, r *http.Request) {
var statusCode int var statusCode int
var resp AddMessageResponse var resp AddMessageResponse
newMessage := message.Message{Content: req.Content, UserId: req.UserId} newMessage := message.Message{Content: req.Content, UserId: &req.UserId}
if isMessageValid(&newMessage) { if isMessageValid(&newMessage) {
ctx := r.Context() ctx := r.Context()
@@ -160,7 +155,7 @@ func (c *MessageHandler) GetMessage(w http.ResponseWriter, r *http.Request) {
if messages, err := c.MessageStore.GetAllMessages(ctx); err == nil { if messages, err := c.MessageStore.GetAllMessages(ctx); err == nil {
log.Println("Amount of messages:", len(messages)) log.Println("Amount of messages:", len(messages))
for _, msg := range messages { for _, msg := range messages {
if msg.UserId == userId { if *msg.UserId == userId {
resp.Data = append(resp.Data, *msg) resp.Data = append(resp.Data, *msg)
} }
} }
+104 -18
View File
@@ -1,11 +1,11 @@
package handler package handler
import ( import (
"log"
"net/http" "net/http"
"time" "time"
"git.kundeng.us/phoenix/textsender-models/tx0/message" "git.kundeng.us/phoenix/textsender-models/tx0/message/event"
"git.kundeng.us/phoenix/textsender-models/tx0/types"
"github.com/google/uuid" "github.com/google/uuid"
"git.kundeng.us/phoenix/textsender-api/internal/app" "git.kundeng.us/phoenix/textsender-api/internal/app"
@@ -22,20 +22,32 @@ func NewEventResponseHandler(apiApp *app.App, str store.MessageEventResponseStor
} }
type RecordEventRequest struct { type RecordEventRequest struct {
ScheduledMessageEventId uuid.UUID `json:"scheduled_message_event_id"` ScheduledMessageEventId uuid.UUID `json:"scheduled_message_event_id"`
Response types.JSONB `json:"response"` Response map[string]any `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"` Status string `json:"status"`
ContactId *uuid.UUID `json:"contact_id"` ContactId *uuid.UUID `json:"contact_id"`
MessageId *uuid.UUID `json:"message_id"` MessageId *uuid.UUID `json:"message_id"`
} }
type RecordEventResponse struct { type RecordEventResponse struct {
Message string `json:"message"` Message string `json:"message"`
Data []*message.MessageEventResponse `json:"data"` Data []*event.MessageEventResponse `json:"data"`
} }
// RecordMessageEventResponse godoc
// @Summary Record MessageEventResponse
// @Description Saves the result of sending a text message (requires JWT)
// @Tags message
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body RecordEventRequest true "Data to add MessageEventResponse"
// @Success 201 {object} RecordEventResponse
// @Failure 400 {object} RecordEventResponse
// @Failure 500 {object} RecordEventResponse
// @Router /schedule/message/event/response/record [post]
func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Request) { func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Request) {
var req RecordEventRequest var req RecordEventRequest
if err := ExtractFromRequest(r, &req); err != nil { if err := ExtractFromRequest(r, &req); err != nil {
@@ -44,7 +56,7 @@ func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Req
defer r.Body.Close() defer r.Body.Close()
var statusCode int var statusCode int
var rp message.MessageEventResponse var rp event.MessageEventResponse
var resp RecordEventResponse var resp RecordEventResponse
if req.Sent == nil { if req.Sent == nil {
@@ -60,24 +72,39 @@ func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Req
statusCode = http.StatusBadRequest statusCode = http.StatusBadRequest
resp.Message = "Scheduled message event Id is empty" resp.Message = "Scheduled message event Id is empty"
} else { } else {
log.Println("Starting process to record Message Event Response")
rp.ScheduledMessageEventId = req.ScheduledMessageEventId rp.ScheduledMessageEventId = req.ScheduledMessageEventId
rp.Response = req.Response rp.Response = req.Response
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 { switch req.Status {
rp.Status = req.Status case event.Message_Event_Response_Status_Instant:
rp.MessageId = req.MessageId {
rp.ContactId = req.ContactId log.Println("Status:", req.Status)
} else if req.Status == message.Message_Event_Response_Status_Scheduled { rp.Status = req.Status
rp.Status = req.Status rp.MessageId = req.MessageId
rp.ContactId = req.ContactId
}
case event.Message_Event_Response_Status_Scheduled:
{
log.Println("Status:", req.Status)
rp.Status = req.Status
}
default:
{
statusCode = http.StatusBadRequest
resp.Message = "Status is invalid"
}
} }
ctx := r.Context() ctx := r.Context()
if err := e.MessageEventResponseStore.Create(ctx, &rp); err != nil { if err := e.MessageEventResponseStore.Create(ctx, &rp); err != nil {
log.Println("Error:", err)
resp.Message = err.Error() resp.Message = err.Error()
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
} else { } else {
log.Println("Created Message Event Response")
statusCode = http.StatusCreated statusCode = http.StatusCreated
resp.Message = "Successful" resp.Message = "Successful"
resp.Data = append(resp.Data, &rp) resp.Data = append(resp.Data, &rp)
@@ -86,3 +113,62 @@ func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Req
RespondWithJSON(w, statusCode, &resp) RespondWithJSON(w, statusCode, &resp)
} }
type GetMessageEventResponseFetchedResponse struct {
Message string `json:"message"`
Data []*event.MessageEventResponse `json:"data"`
}
// FetchtMessageEventResponse godoc
// @Summary Fetcht MessageEventResponse
// @Description Fetches a MessageEventResponse given a user id (requires JWT)
// @Tags message
// @Accept json
// @Produce json
// @Param user_id path string true "User Id"
// @Security BearerAuth
// @Success 200 {object} GetMessageEventResponseFetchedResponse
// @Failure 400 {object} GetMessageEventResponseFetchedResponse
// @Failure 500 {object} GetMessageEventResponseFetchedResponse
// @Router /schedule/message/event/response [get]
func (e *EventResponseHandler) Fetch(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()
var userId uuid.UUID
if _, exists := queryParams["user_id"]; !exists {
http.Error(w, "Query params", http.StatusBadRequest)
return
} else {
var err error
userId, err = uuid.Parse(queryParams.Get("user_id"))
if err != nil {
http.Error(w, "Query params", http.StatusBadRequest)
return
}
}
var statusCode int
var resp GetMessageEventResponseFetchedResponse
if userId == uuid.Nil {
statusCode = http.StatusBadRequest
resp.Message = "User Id is nil"
} else {
ctx := r.Context()
if sentMessages, err := e.MessageEventResponseStore.GetWithUserId(ctx, userId); err != nil {
statusCode = http.StatusInternalServerError
resp.Message = err.Error()
} else {
if len(sentMessages) == 0 {
statusCode = http.StatusNotFound
resp.Message = "Not MessageEventResponses found"
} else {
statusCode = http.StatusOK
resp.Message = "Successful"
resp.Data = sentMessages
}
}
}
RespondWithJSON(w, statusCode, &resp)
}
+88 -10
View File
@@ -2,13 +2,14 @@ package handler
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"testing" "testing"
"time" "time"
"git.kundeng.us/phoenix/textsender-models/tx0/message" evnt "git.kundeng.us/phoenix/textsender-models/tx0/message/event"
"git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -30,14 +31,14 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) {
assert.NoError(t, err, "Error getting app") assert.NoError(t, err, "Error getting app")
handler := NewEventResponseHandler(apiApp, merStore) handler := NewEventResponseHandler(apiApp, merStore)
recipientId := uuid.New() contactId := uuid.New()
messageId := uuid.New() messageId := uuid.New()
scheduledMessageId := uuid.New() scheduledMessageId := uuid.New()
testUserId := uuid.New() testUserId := uuid.New()
con := testContact(recipientId, testUserId) con := ContactTest(contactId, testUserId)
msg := testMessage(messageId, testUserId) msg := MessageTest(messageId, testUserId)
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
event := scheduling.ScheduledMessageEvent{} event := scheduling.ScheduledMessageEvent{}
ctx := t.Context() ctx := t.Context()
@@ -50,17 +51,19 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) {
assert.NoError(t, err, "Error creating scheduled message: %v", err) assert.NoError(t, err, "Error creating scheduled message: %v", err)
} }
event.MessageId = msg.Id event.MessageId = *msg.Id
event.RecipientId = *con.Id event.ContactId = *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)
} }
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\"}}") var merResponse map[string]any
merResponse, err = ConvertStringToMapOfAny(MERResponseInString())
assert.NoError(t, err, "Error Converting to map[string]any")
testReq := RecordEventRequest{ScheduledMessageEventId: event.Id, Response: bytes, UserId: schMsg.UserId, Sent: &sent, Status: message.Message_Event_Response_Status_Scheduled} testReq := RecordEventRequest{ScheduledMessageEventId: event.Id, Response: merResponse, UserId: schMsg.UserId, Sent: &sent, Status: evnt.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)))
@@ -76,9 +79,84 @@ func TestRecordMessageEventResponseWithMock(t *testing.T) {
assert.NotEmpty(t, response.Data, "No message event response created") assert.NotEmpty(t, response.Data, "No message event response created")
var msgEventResp message.MessageEventResponse var msgEventResp evnt.MessageEventResponse
msgEventResp = *response.Data[0] msgEventResp = *response.Data[0]
assert.NotEmpty(t, msgEventResp.Response, "The response of the message event response is empty") assert.NotEmpty(t, msgEventResp.Response, "The response of the message event response is empty")
assert.Equal(t, msgEventResp.Response, testReq.Response, "Responses do not match") assert.Equal(t, msgEventResp.Response, testReq.Response, "Responses do not match")
} }
func TestGetMessageEventResponse(t *testing.T) {
now := time.Now()
mockStore := mock.NewMockScheduledMessageEventStore()
contactStore := mock.NewMockContactStore()
messageStore := mock.NewMockMessageStore()
schMsgStore := mock.NewMockScheduledMessageStore()
merStore := mock.NewMockMessageEventResponseStore()
apiApp, err := GetApp()
assert.NoError(t, err, "Error getting app")
handler := NewEventResponseHandler(apiApp, merStore)
contactId := uuid.New()
messageId := uuid.New()
scheduledMessageId := uuid.New()
testUserId := uuid.New()
con := ContactTest(contactId, testUserId)
msg := MessageTest(messageId, testUserId)
schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
ctx := t.Context()
if err := contactStore.CreateContact(ctx, &con); err != nil {
assert.NoError(t, err, "Error creating contact: %v", err)
} else if err = messageStore.CreateMessage(ctx, &msg); err != nil {
assert.NoError(t, err, "Error creating message: %v", err)
} else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil {
assert.NoError(t, err, "Error creating scheduled message: %v", err)
}
event := scheduling.ScheduledMessageEvent{}
event.MessageId = *msg.Id
event.ContactId = *con.Id
event.ScheduledMessageId = schMsg.Id
sent := now.Add(30 * time.Minute)
if err := mockStore.CreateScheduledMessageEvent(ctx, &event); err != nil {
assert.NoError(t, err, "Error creating scheduled message event: %v", err)
} else {
m := evnt.MessageEventResponse{}
m.ScheduledMessageEventId = event.Id
m.UserId = schMsg.UserId
m.Status = evnt.Message_Event_Response_Status_Scheduled
m.Sent = sent
var merResponse map[string]any
merResponse, err = ConvertStringToMapOfAny(MERResponseInString())
assert.NoError(t, err, "Error Converting to map[string]any")
m.Response = merResponse
if err := merStore.Create(ctx, &m); err != nil {
assert.NoError(t, err, "Error creating MessageEventResponse: %v", err)
}
}
url := fmt.Sprintf("%s?user_id=%s", endpoint.FetchMessageEventResponse, testUserId)
req, _ := http.NewRequest("GET", url, nil)
rr := httptest.NewRecorder()
handler.Fetch(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var response GetMessageEventResponseFetchedResponse
err = json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err, "Error parsing message event response: %v", err)
assert.NotEmpty(t, response.Data, "No message event response found")
var msgEventResp evnt.MessageEventResponse
msgEventResp = *response.Data[0]
assert.NotEmpty(t, msgEventResp.Response, "The response of the message event response is empty")
}
+1 -1
View File
@@ -51,7 +51,7 @@ func TestGetMessageWithMock(t *testing.T) {
mockstore := mock.NewMockMessageStore() mockstore := mock.NewMockMessageStore()
testUserId := uuid.New() testUserId := uuid.New()
testCon := message.Message{Content: "Who is the one that benefits?", UserId: testUserId} testCon := message.Message{Content: "Who is the one that benefits?", UserId: &testUserId}
ctx := t.Context() ctx := t.Context()
if err := mockstore.CreateMessage(ctx, &testCon); err != nil { if err := mockstore.CreateMessage(ctx, &testCon); err != nil {
assert.NoError(t, err, "Error creating message") assert.NoError(t, err, "Error creating message")
+10 -10
View File
@@ -10,6 +10,16 @@ import (
"git.kundeng.us/phoenix/textsender-api/internal/store" "git.kundeng.us/phoenix/textsender-api/internal/store"
) )
type ScheduledMessageStatusHandler struct {
App *app.App
ScheduledMessageEventStore store.ScheduledMessageEventStore
ScheduledMessageStore store.ScheduledMessageStore
}
func NewScheduledMessageStatusHandler(apiApp *app.App, str store.ScheduledMessageEventStore, schStore store.ScheduledMessageStore) *ScheduledMessageStatusHandler {
return &ScheduledMessageStatusHandler{App: apiApp, ScheduledMessageEventStore: str, ScheduledMessageStore: schStore}
}
type RequestScheduledMessageStatus struct { type RequestScheduledMessageStatus struct {
ScheduledMessageId uuid.UUID `json:"scheduled_message_id"` ScheduledMessageId uuid.UUID `json:"scheduled_message_id"`
Status string `json:"status"` Status string `json:"status"`
@@ -25,16 +35,6 @@ type ScheduledMessageStatusResponse struct {
Data []ScheduledMessageChange `json:"data"` Data []ScheduledMessageChange `json:"data"`
} }
type ScheduledMessageStatusHandler struct {
App *app.App
ScheduledMessageEventStore store.ScheduledMessageEventStore
ScheduledMessageStore store.ScheduledMessageStore
}
func NewScheduledMessageStatusHandler(apiApp *app.App, str store.ScheduledMessageEventStore, schStore store.ScheduledMessageStore) *ScheduledMessageStatusHandler {
return &ScheduledMessageStatusHandler{App: apiApp, ScheduledMessageEventStore: str, ScheduledMessageStore: schStore}
}
// UpdateStatus godoc // UpdateStatus godoc
// @Summary Update status // @Summary Update status
// @Description Update the status of a scheduled message (requires JWT) // @Description Update the status of a scheduled message (requires JWT)
@@ -24,15 +24,15 @@ func TestUpdateScheduledMessageStatusWithMock(t *testing.T) {
messageStore := mock.NewMockMessageStore() messageStore := mock.NewMockMessageStore()
schMsgStore := mock.NewMockScheduledMessageStore() schMsgStore := mock.NewMockScheduledMessageStore()
recipientId := uuid.New() contactId := uuid.New()
messageId := uuid.New() messageId := uuid.New()
scheduledMessageId := uuid.New() scheduledMessageId := uuid.New()
testUserId := uuid.New() testUserId := uuid.New()
con := testContact(recipientId, testUserId) con := ContactTest(contactId, testUserId)
msg := testMessage(messageId, testUserId) msg := MessageTest(messageId, testUserId)
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id)
ctx := t.Context() ctx := t.Context()
+19 -19
View File
@@ -13,6 +13,15 @@ import (
"git.kundeng.us/phoenix/textsender-api/internal/store" "git.kundeng.us/phoenix/textsender-api/internal/store"
) )
type ScheduledMessageHandler struct {
App *app.App
ScheduledMessageStore store.ScheduledMessageStore
}
func NewScheduledMessageHandler(apiApp *app.App, str store.ScheduledMessageStore) *ScheduledMessageHandler {
return &ScheduledMessageHandler{App: apiApp, ScheduledMessageStore: str}
}
type RequestAddScheduledMessage struct { type RequestAddScheduledMessage struct {
Scheduled time.Time `json:"scheduled"` Scheduled time.Time `json:"scheduled"`
Status string `json:"status"` Status string `json:"status"`
@@ -24,25 +33,6 @@ type AddScheduledMessageResponse struct {
Data []scheduling.ScheduledMessage `json:"data"` Data []scheduling.ScheduledMessage `json:"data"`
} }
type GetScheduledMessageResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessage `json:"data"`
}
type FetchNextMessageResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessage `json:"data"`
}
type ScheduledMessageHandler struct {
App *app.App
ScheduledMessageStore store.ScheduledMessageStore
}
func NewScheduledMessageHandler(apiApp *app.App, str store.ScheduledMessageStore) *ScheduledMessageHandler {
return &ScheduledMessageHandler{App: apiApp, ScheduledMessageStore: str}
}
// AddScheduledMessage godoc // AddScheduledMessage godoc
// @Summary Add scheduled message // @Summary Add scheduled message
// @Description Adds a scheduled message (requires JWT) // @Description Adds a scheduled message (requires JWT)
@@ -106,6 +96,11 @@ func (s *ScheduledMessageHandler) AddScheduledMessage(w http.ResponseWriter, r *
RespondWithJSON(w, statusCode, &resp) RespondWithJSON(w, statusCode, &resp)
} }
type GetScheduledMessageResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessage `json:"data"`
}
// GetScheduledMessage godoc // GetScheduledMessage godoc
// @Summary Get scheduled message // @Summary Get scheduled message
// @Description Get a scheduled message (requires JWT) // @Description Get a scheduled message (requires JWT)
@@ -183,6 +178,11 @@ func (s *ScheduledMessageHandler) GetScheduledMessage(w http.ResponseWriter, r *
RespondWithJSON(w, statusCode, &resp) RespondWithJSON(w, statusCode, &resp)
} }
type FetchNextMessageResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessage `json:"data"`
}
// FetchNextMessage godoc // FetchNextMessage godoc
// @Summary Fetch scheduled message // @Summary Fetch scheduled message
// @Description Fetches a scheduled message that is available to be processed (requires JWT) // @Description Fetches a scheduled message that is available to be processed (requires JWT)
+25 -25
View File
@@ -12,27 +12,6 @@ import (
"git.kundeng.us/phoenix/textsender-api/internal/store" "git.kundeng.us/phoenix/textsender-api/internal/store"
) )
type RequestAddScheduledMessageEvent struct {
RecipientId uuid.UUID `json:"recipient_id"`
MessageId uuid.UUID `json:"message_id"`
ScheduledMessageId uuid.UUID `json:"scheduled_message_id"`
}
type AddScheduledMessageEventResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessageEvent `json:"data"`
}
type GetScheduledMessageEventResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessageEvent `json:"data"`
}
type DeleteScheduledMessageEventResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessageEvent `json:"data"`
}
type ScheduledMessageEventHandler struct { type ScheduledMessageEventHandler struct {
App *app.App App *app.App
ScheduledMessageEventStore store.ScheduledMessageEventStore ScheduledMessageEventStore store.ScheduledMessageEventStore
@@ -43,7 +22,18 @@ func NewScheduledMessageEventHandler(apiApp *app.App, str store.ScheduledMessage
return &ScheduledMessageEventHandler{App: apiApp, ScheduledMessageEventStore: str, ScheduledMessageStore: schStore} return &ScheduledMessageEventHandler{App: apiApp, ScheduledMessageEventStore: str, ScheduledMessageStore: schStore}
} }
// AddContact godoc type RequestAddScheduledMessageEvent struct {
ContactId uuid.UUID `json:"contact_id"`
MessageId uuid.UUID `json:"message_id"`
ScheduledMessageId uuid.UUID `json:"scheduled_message_id"`
}
type AddScheduledMessageEventResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessageEvent `json:"data"`
}
// AddScheduledMessageEvent godoc
// @Summary Add scheduled message event // @Summary Add scheduled message event
// @Description Add a scheduled message event (requires JWT) // @Description Add a scheduled message event (requires JWT)
// @Tags scheduled message events // @Tags scheduled message events
@@ -67,14 +57,14 @@ func (s *ScheduledMessageEventHandler) AddScheduledMessageEvent(w http.ResponseW
} }
defer r.Body.Close() defer r.Body.Close()
event := scheduling.ScheduledMessageEvent{RecipientId: req.RecipientId, MessageId: req.MessageId, ScheduledMessageId: req.ScheduledMessageId} event := scheduling.ScheduledMessageEvent{ContactId: req.ContactId, MessageId: req.MessageId, ScheduledMessageId: req.ScheduledMessageId}
var statusCode int var statusCode int
var resp AddScheduledMessageEventResponse var resp AddScheduledMessageEventResponse
if event.RecipientId == uuid.Nil { if event.ContactId == uuid.Nil {
statusCode = http.StatusBadRequest statusCode = http.StatusBadRequest
resp.Message = "Recipient Id is nil" resp.Message = "Contact Id is nil"
} else if event.MessageId == uuid.Nil { } else if event.MessageId == uuid.Nil {
statusCode = http.StatusBadRequest statusCode = http.StatusBadRequest
resp.Message = "Message Id is nil" resp.Message = "Message Id is nil"
@@ -117,6 +107,11 @@ func (s *ScheduledMessageEventHandler) AddScheduledMessageEvent(w http.ResponseW
RespondWithJSON(w, statusCode, &resp) RespondWithJSON(w, statusCode, &resp)
} }
type GetScheduledMessageEventResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessageEvent `json:"data"`
}
// GetScheduledMessageEvent godoc // GetScheduledMessageEvent godoc
// @Summary Get scheduled message event // @Summary Get scheduled message event
// @Description Gets a scheduled message event (requires JWT) // @Description Gets a scheduled message event (requires JWT)
@@ -181,6 +176,11 @@ func (s *ScheduledMessageEventHandler) GetScheduledMessageEvent(w http.ResponseW
RespondWithJSON(w, statusCode, &resp) RespondWithJSON(w, statusCode, &resp)
} }
type DeleteScheduledMessageEventResponse struct {
Message string `json:"message"`
Data []scheduling.ScheduledMessageEvent `json:"data"`
}
// DeleteScheduledMessageEvent godoc // DeleteScheduledMessageEvent godoc
// @Summary Delete scheduled message event // @Summary Delete scheduled message event
// @Description Deletes a scheduled message event (requires JWT) // @Description Deletes a scheduled message event (requires JWT)
@@ -9,8 +9,6 @@ import (
"testing" "testing"
"time" "time"
"git.kundeng.us/phoenix/textsender-models/tx0/contact"
"git.kundeng.us/phoenix/textsender-models/tx0/message"
"git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -20,14 +18,14 @@ import (
) )
var ( var (
recipientId = uuid.New() contactId = uuid.New()
messageId = uuid.New() messageId = uuid.New()
scheduledMessageId = uuid.New() scheduledMessageId = uuid.New()
testUserId = uuid.New() testUserId = uuid.New()
) )
type CreateScheduledMessageEventRequest struct { type CreateScheduledMessageEventRequest struct {
RecipientId uuid.UUID `json:"recipient_id"` ContactId uuid.UUID `json:"contact_id"`
MessageId uuid.UUID `json:"message_id"` MessageId uuid.UUID `json:"message_id"`
ScheduledMessageId uuid.UUID `json:"scheduled_message_id"` ScheduledMessageId uuid.UUID `json:"scheduled_message_id"`
} }
@@ -44,14 +42,14 @@ func TestCreateScheduledMessageEventWithMock(t *testing.T) {
assert.NoError(t, err, "Error getting app") assert.NoError(t, err, "Error getting app")
handler := NewScheduledMessageEventHandler(apiApp, mockStore, schMsgStore) handler := NewScheduledMessageEventHandler(apiApp, mockStore, schMsgStore)
recipientId := uuid.New() contactId := uuid.New()
messageId := uuid.New() messageId := uuid.New()
scheduledMessageId := uuid.New() scheduledMessageId := uuid.New()
testUserId := uuid.New() testUserId := uuid.New()
con := testContact(recipientId, testUserId) con := ContactTest(contactId, testUserId)
msg := testMessage(messageId, testUserId) msg := MessageTest(messageId, testUserId)
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
ctx := t.Context() ctx := t.Context()
@@ -63,7 +61,7 @@ func TestCreateScheduledMessageEventWithMock(t *testing.T) {
assert.NoError(t, err, "Error creating scheduled message: %v", err) assert.NoError(t, err, "Error creating scheduled message: %v", err)
} }
testReq := CreateScheduledMessageEventRequest{RecipientId: recipientId, MessageId: messageId, ScheduledMessageId: scheduledMessageId} testReq := CreateScheduledMessageEventRequest{ContactId: contactId, MessageId: messageId, ScheduledMessageId: scheduledMessageId}
jsonValue, _ := json.Marshal(testReq) jsonValue, _ := json.Marshal(testReq)
req, _ := http.NewRequest("POST", endpoint.AddEventToScheduledMessageEndpoint, strings.NewReader(string(jsonValue))) req, _ := http.NewRequest("POST", endpoint.AddEventToScheduledMessageEndpoint, strings.NewReader(string(jsonValue)))
@@ -96,15 +94,15 @@ func TestGetScheduledMessageEventWithMock(t *testing.T) {
assert.NoError(t, err, "Error getting app") assert.NoError(t, err, "Error getting app")
handler := NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schMsgStore) handler := NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schMsgStore)
recipientId := uuid.New() contactId := uuid.New()
messageId := uuid.New() messageId := uuid.New()
scheduledMessageId := uuid.New() scheduledMessageId := uuid.New()
testUserId := uuid.New() testUserId := uuid.New()
con := testContact(recipientId, testUserId) con := ContactTest(contactId, testUserId)
msg := testMessage(messageId, testUserId) msg := MessageTest(messageId, testUserId)
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id)
ctx := t.Context() ctx := t.Context()
@@ -147,10 +145,10 @@ func TestDeleteScheduledMessageEventWithMock(t *testing.T) {
assert.NoError(t, err, "Error getting app") assert.NoError(t, err, "Error getting app")
handler := NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schMsgStore) handler := NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schMsgStore)
con := testContact(recipientId, testUserId) con := ContactTest(contactId, testUserId)
msg := testMessage(messageId, testUserId) msg := MessageTest(messageId, testUserId)
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id)
ctx := t.Context() ctx := t.Context()
@@ -182,32 +180,3 @@ func TestDeleteScheduledMessageEventWithMock(t *testing.T) {
assert.NotEmpty(t, msgEvent.Created, "Created date should not be empty") assert.NotEmpty(t, msgEvent.Created, "Created date should not be empty")
} }
func testContact(id uuid.UUID, userId uuid.UUID) contact.Contact {
if id == uuid.Nil {
id = uuid.New()
return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId}
} else {
return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId}
}
}
func testMessage(id uuid.UUID, userId uuid.UUID) message.Message {
if id == uuid.Nil {
return message.Message{Id: uuid.New(), Content: "Oh how the mighty have fallen", UserId: userId}
} else {
return message.Message{Id: id, Content: "Oh how the mighty have fallen", UserId: userId}
}
}
func testScheduledMessage(id uuid.UUID, userId uuid.UUID, now time.Time) scheduling.ScheduledMessage {
if id == uuid.Nil {
return scheduling.ScheduledMessage{Id: uuid.New(), UserId: userId, Scheduled: now.Add(20 * time.Minute), Status: scheduling.Pending}
} else {
return scheduling.ScheduledMessage{Id: id, UserId: userId, Scheduled: now.Add(20 * time.Minute), Status: scheduling.Pending}
}
}
func testScheduledMessageEvent(messageId, recipientId, scheduledMessageId uuid.UUID) scheduling.ScheduledMessageEvent {
return scheduling.ScheduledMessageEvent{MessageId: messageId, RecipientId: recipientId, ScheduledMessageId: scheduledMessageId}
}
+8 -8
View File
@@ -31,7 +31,7 @@ func TestCreateScheduledMessageWithMock(t *testing.T) {
handler := NewScheduledMessageHandler(apiApp, mockStore) handler := NewScheduledMessageHandler(apiApp, mockStore)
testUserId := uuid.New() testUserId := uuid.New()
testBody := testCreateScheduledMessageRequest(testUserId, now) testBody := createScheduledMessageRequest(testUserId, now)
jsonValue, _ := json.Marshal(testBody) jsonValue, _ := json.Marshal(testBody)
req, _ := http.NewRequest("POST", endpoint.ScheduleMessageEndpoint, strings.NewReader(string(jsonValue))) req, _ := http.NewRequest("POST", endpoint.ScheduleMessageEndpoint, strings.NewReader(string(jsonValue)))
@@ -55,7 +55,7 @@ func TestGetScheduledMessageWithMock(t *testing.T) {
mockStore := mock.NewMockScheduledMessageStore() mockStore := mock.NewMockScheduledMessageStore()
testUserId := uuid.New() testUserId := uuid.New()
testBody := testCreateScheduledMessageRequest(testUserId, now) testBody := createScheduledMessageRequest(testUserId, now)
ctx := t.Context() ctx := t.Context()
schMsg := scheduling.ScheduledMessage{} schMsg := scheduling.ScheduledMessage{}
@@ -89,15 +89,15 @@ func TestFetchScheduledMessageWithMock(t *testing.T) {
messageStore := mock.NewMockMessageStore() messageStore := mock.NewMockMessageStore()
schMsgEventStore := mock.NewMockScheduledMessageEventStore() schMsgEventStore := mock.NewMockScheduledMessageEventStore()
recipientId := uuid.New() contactId := uuid.New()
messageId := uuid.New() messageId := uuid.New()
scheduledMessageId := uuid.New() scheduledMessageId := uuid.New()
testUserId := uuid.New() testUserId := uuid.New()
con := testContact(recipientId, testUserId) con := ContactTest(contactId, testUserId)
msg := testMessage(messageId, testUserId) msg := MessageTest(messageId, testUserId)
schMsg := testScheduledMessage(scheduledMessageId, testUserId, now) schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now)
event := testScheduledMessageEvent(msg.Id, *con.Id, schMsg.Id) event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id)
ctx := t.Context() ctx := t.Context()
@@ -135,7 +135,7 @@ func TestFetchScheduledMessageWithMock(t *testing.T) {
assert.Equal(t, scheduling.Processing, fetchedSchMsg.Status, "The statuses do not match") assert.Equal(t, scheduling.Processing, fetchedSchMsg.Status, "The statuses do not match")
} }
func testCreateScheduledMessageRequest(userId uuid.UUID, now time.Time) CreateScheduledMessageRequest { func createScheduledMessageRequest(userId uuid.UUID, now time.Time) CreateScheduledMessageRequest {
scheduled := now.Add(5 * time.Minute) scheduled := now.Add(5 * time.Minute)
return CreateScheduledMessageRequest{Status: scheduling.Pending, UserId: userId, Scheduled: scheduled} return CreateScheduledMessageRequest{Status: scheduling.Pending, UserId: userId, Scheduled: scheduled}
} }
+32 -7
View File
@@ -4,13 +4,14 @@ import (
"context" "context"
"fmt" "fmt"
"git.kundeng.us/phoenix/textsender-models/tx0/message" "git.kundeng.us/phoenix/textsender-models/tx0/message/event"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )
type MessageEventResponseStore interface { type MessageEventResponseStore interface {
Create(ctx context.Context, mer *message.MessageEventResponse) error Create(ctx context.Context, mer *event.MessageEventResponse) error
GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*event.MessageEventResponse, error)
} }
type PGMessageEventResponseStore struct { type PGMessageEventResponseStore struct {
@@ -21,16 +22,40 @@ func NewMessageEventResponseStore(db *pgxpool.Pool) *PGMessageEventResponseStore
return &PGMessageEventResponseStore{db: db} return &PGMessageEventResponseStore{db: db}
} }
func (m *PGMessageEventResponseStore) Create(ctx context.Context, mer *message.MessageEventResponse) error { func (m *PGMessageEventResponseStore) Create(ctx context.Context, mer *event.MessageEventResponse) error {
var query string var query string
queryBase := "INSERT INTO message_event_responses" queryBase := "INSERT INTO message_event_responses"
queryReturn := "RETURNING id" queryReturn := "RETURNING id"
if mer.ScheduledMessageEventId == uuid.Nil { if mer.ScheduledMessageEventId == uuid.Nil {
query = fmt.Sprintf("%s (response, user_id, sent, contact_id, message_id) VALUES ($1, $2, $3, $4, $5) %s", queryBase, queryReturn) query = fmt.Sprintf("%s (response, user_id, sent, contact_id, message_id, status) VALUES ($1, $2, $3, $4, $5, $6) %s", queryBase, queryReturn)
return m.db.QueryRow(ctx, query, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId).Scan(&mer.Id) return m.db.QueryRow(ctx, query, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId, mer.Status).Scan(&mer.Id)
} else { } else {
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) query = fmt.Sprintf("%s (scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status) VALUES ($1, $2, $3, $4, $5, $6, $7) %s", queryBase, queryReturn)
return m.db.QueryRow(ctx, query, mer.ScheduledMessageEventId, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId).Scan(&mer.Id) return m.db.QueryRow(ctx, query, mer.ScheduledMessageEventId, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId, mer.Status).Scan(&mer.Id)
} }
} }
func (m *PGMessageEventResponseStore) GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*event.MessageEventResponse, error) {
query := "SELECT id, scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status FROM message_event_responses WHERE user_id = $1"
rows, err := m.db.Query(ctx, query, userId)
if err != nil {
return nil, fmt.Errorf("Error querying: %w", err)
}
defer rows.Close()
var sentMessages []*event.MessageEventResponse
for rows.Next() {
var msg event.MessageEventResponse
if err := rows.Scan(&msg.Id, &msg.ScheduledMessageEventId, &msg.Response, &msg.UserId, &msg.Sent, &msg.ContactId, &msg.MessageId, &msg.Status); err != nil {
return nil, fmt.Errorf("Scanning row: %w", err)
}
sentMessages = append(sentMessages, &msg)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("Iterating message_event_responses: %w", err)
}
return sentMessages, nil
}
@@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
"git.kundeng.us/phoenix/textsender-models/tx0/message" "git.kundeng.us/phoenix/textsender-models/tx0/message/event"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -15,20 +15,20 @@ type MessageEventResponseKey struct {
UserId uuid.UUID UserId uuid.UUID
} }
type MockMessageEventResponseStore struct { type MockMessageEventResponseStore struct {
MessageEventResponses map[uuid.UUID]*message.MessageEventResponse MessageEventResponses map[uuid.UUID]*event.MessageEventResponse
MessageEventResponsesByKey map[MessageEventResponseKey]*message.MessageEventResponse MessageEventResponsesByKey map[MessageEventResponseKey]*event.MessageEventResponse
mu sync.RWMutex mu sync.RWMutex
Error error Error error
} }
func NewMockMessageEventResponseStore() *MockMessageEventResponseStore { func NewMockMessageEventResponseStore() *MockMessageEventResponseStore {
return &MockMessageEventResponseStore{ return &MockMessageEventResponseStore{
MessageEventResponses: make(map[uuid.UUID]*message.MessageEventResponse), MessageEventResponses: make(map[uuid.UUID]*event.MessageEventResponse),
MessageEventResponsesByKey: make(map[MessageEventResponseKey]*message.MessageEventResponse), MessageEventResponsesByKey: make(map[MessageEventResponseKey]*event.MessageEventResponse),
} }
} }
func (m *MockMessageEventResponseStore) Create(ctx context.Context, mer *message.MessageEventResponse) error { func (m *MockMessageEventResponseStore) Create(ctx context.Context, mer *event.MessageEventResponse) error {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
@@ -60,3 +60,22 @@ func (m *MockMessageEventResponseStore) Create(ctx context.Context, mer *message
return nil return nil
} }
} }
// TODO: Add code to get mock MessageEventResponse
func (m *MockMessageEventResponseStore) GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*event.MessageEventResponse, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.Error != nil {
return nil, m.Error
}
var messages []*event.MessageEventResponse
for _, msg := range m.MessageEventResponses {
if msg.UserId == userId {
messages = append(messages, msg)
}
}
return messages, nil
}
@@ -11,7 +11,7 @@ import (
) )
type ScheduledMessageEventKey struct { type ScheduledMessageEventKey struct {
RecipientId uuid.UUID ContactId uuid.UUID
MessageId uuid.UUID MessageId uuid.UUID
ScheduledMessageId uuid.UUID ScheduledMessageId uuid.UUID
} }
@@ -94,7 +94,7 @@ func (m *MockScheduledMessageEventStore) Delete(ctx context.Context, id uuid.UUI
copiedEventsKey := make(map[ScheduledMessageEventKey]*scheduling.ScheduledMessageEvent) copiedEventsKey := make(map[ScheduledMessageEventKey]*scheduling.ScheduledMessageEvent)
for i, schMsgEvent := range m.ScheduledMessageEvents { for i, schMsgEvent := range m.ScheduledMessageEvents {
key := ScheduledMessageEventKey{RecipientId: schMsgEvent.RecipientId, MessageId: schMsgEvent.MessageId, ScheduledMessageId: schMsgEvent.ScheduledMessageId} key := ScheduledMessageEventKey{ContactId: schMsgEvent.ContactId, MessageId: schMsgEvent.MessageId, ScheduledMessageId: schMsgEvent.ScheduledMessageId}
if schMsgEvent.Id != id { if schMsgEvent.Id != id {
copiedEvents[i] = schMsgEvent copiedEvents[i] = schMsgEvent
copiedEventsKey[key] = schMsgEvent copiedEventsKey[key] = schMsgEvent
@@ -132,7 +132,7 @@ func (m *MockScheduledMessageEventStore) CreateScheduledMessageEvent(ctx context
event.Id = uuid.New() event.Id = uuid.New()
} }
key := ScheduledMessageEventKey{RecipientId: event.RecipientId, MessageId: event.MessageId, ScheduledMessageId: event.ScheduledMessageId} key := ScheduledMessageEventKey{ContactId: event.ContactId, MessageId: event.MessageId, ScheduledMessageId: event.ScheduledMessageId}
if _, exists := m.ScheduledMessageEventsByKey[key]; exists { if _, exists := m.ScheduledMessageEventsByKey[key]; exists {
return fmt.Errorf("Already exists") return fmt.Errorf("Already exists")
@@ -152,7 +152,7 @@ func (m *MockScheduledMessageEventStore) Exists(ctx context.Context, event *sche
return false, m.Error return false, m.Error
} }
key := ScheduledMessageEventKey{RecipientId: event.RecipientId, MessageId: event.MessageId, ScheduledMessageId: event.ScheduledMessageId} key := ScheduledMessageEventKey{ContactId: event.ContactId, MessageId: event.MessageId, ScheduledMessageId: event.ScheduledMessageId}
_, exists := m.ScheduledMessageEventsByKey[key] _, exists := m.ScheduledMessageEventsByKey[key]
return exists, nil return exists, nil
+12 -5
View File
@@ -3,6 +3,7 @@ package mock
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"sync" "sync"
"git.kundeng.us/phoenix/textsender-models/tx0/message" "git.kundeng.us/phoenix/textsender-models/tx0/message"
@@ -36,16 +37,22 @@ func (m *MockMessageStore) CreateMessage(ctx context.Context, msg *message.Messa
return m.Error return m.Error
} }
if msg.Id == uuid.Nil { var id uuid.UUID
msg.Id = uuid.New() if msg != nil {
if msg.Id == nil || *msg.Id == uuid.Nil {
id = uuid.New()
msg.Id = &id
}
} else {
return fmt.Errorf("Message not populated")
} }
key := MessageKey{Content: msg.Content, UserId: msg.UserId} key := MessageKey{Content: msg.Content, UserId: *msg.UserId}
if _, exists := m.MessagesByKey[key]; exists { if _, exists := m.MessagesByKey[key]; exists {
return errors.New("Message already exists") return errors.New("Message already exists")
} }
m.Messages[msg.Id] = msg m.Messages[*msg.Id] = msg
m.MessagesByKey[key] = msg m.MessagesByKey[key] = msg
return nil return nil
} }
@@ -88,7 +95,7 @@ func (m *MockMessageStore) MessageExists(ctx context.Context, msg *message.Messa
return false, m.Error return false, m.Error
} }
_, exists := m.MessagesByKey[MessageKey{Content: msg.Content, UserId: msg.UserId}] _, exists := m.MessagesByKey[MessageKey{Content: msg.Content, UserId: *msg.UserId}]
return exists, nil return exists, nil
} }
@@ -27,11 +27,11 @@ func NewScheduledMessageEventStore(db *pgxpool.Pool) *PGScheduledMessageEventSto
} }
func (s *PGScheduledMessageEventStore) Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessageEvent, error) { func (s *PGScheduledMessageEventStore) Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessageEvent, error) {
query := `SELECT id, recipient_id, message_id, scheduled_message_id, created FROM scheduled_message_events WHERE id = $1` query := `SELECT id, contact_id, message_id, scheduled_message_id, created FROM scheduled_message_events WHERE id = $1`
var event scheduling.ScheduledMessageEvent var event scheduling.ScheduledMessageEvent
err := s.db.QueryRow(ctx, query, id).Scan( err := s.db.QueryRow(ctx, query, id).Scan(
&event.Id, &event.RecipientId, &event.MessageId, &event.ScheduledMessageId, &event.Created, &event.Id, &event.ContactId, &event.MessageId, &event.ScheduledMessageId, &event.Created,
) )
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
@@ -45,7 +45,7 @@ func (s *PGScheduledMessageEventStore) Get(ctx context.Context, id uuid.UUID) (*
func (s *PGScheduledMessageEventStore) GetWithScheduleMessageId(ctx context.Context, schMsgId uuid.UUID) ([]*scheduling.ScheduledMessageEvent, error) { func (s *PGScheduledMessageEventStore) GetWithScheduleMessageId(ctx context.Context, schMsgId uuid.UUID) ([]*scheduling.ScheduledMessageEvent, error) {
var events []*scheduling.ScheduledMessageEvent var events []*scheduling.ScheduledMessageEvent
query := `SELECT id, recipient_id, message_id, scheduled_message_id, created FROM scheduled_message_events WHERE scheduled_message_id = $1 query := `SELECT id, contact_id, message_id, scheduled_message_id, created FROM scheduled_message_events WHERE scheduled_message_id = $1
` `
rows, err := s.db.Query(ctx, query, schMsgId) rows, err := s.db.Query(ctx, query, schMsgId)
@@ -57,7 +57,7 @@ func (s *PGScheduledMessageEventStore) GetWithScheduleMessageId(ctx context.Cont
for rows.Next() { for rows.Next() {
var event scheduling.ScheduledMessageEvent var event scheduling.ScheduledMessageEvent
if err := rows.Scan(&event.Id, &event.RecipientId, &event.MessageId, &event.ScheduledMessageId, &event.Created); err != nil { if err := rows.Scan(&event.Id, &event.ContactId, &event.MessageId, &event.ScheduledMessageId, &event.Created); err != nil {
return nil, fmt.Errorf("Error fetching data from row: %w", err) return nil, fmt.Errorf("Error fetching data from row: %w", err)
} else { } else {
events = append(events, &event) events = append(events, &event)
@@ -90,21 +90,21 @@ func (s *PGScheduledMessageEventStore) Delete(ctx context.Context, id uuid.UUID)
func (s *PGScheduledMessageEventStore) CreateScheduledMessageEvent(ctx context.Context, event *scheduling.ScheduledMessageEvent) error { func (s *PGScheduledMessageEventStore) CreateScheduledMessageEvent(ctx context.Context, event *scheduling.ScheduledMessageEvent) error {
query := ` query := `
INSERT INTO scheduled_message_events (recipient_id, message_id, scheduled_message_id) INSERT INTO scheduled_message_events (contact_id, message_id, scheduled_message_id)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
RETURNING id, created RETURNING id, created
` `
return s.db.QueryRow(ctx, query, event.RecipientId, event.MessageId, event.ScheduledMessageId).Scan( return s.db.QueryRow(ctx, query, event.ContactId, event.MessageId, event.ScheduledMessageId).Scan(
&event.Id, &event.Created, &event.Id, &event.Created,
) )
} }
func (s *PGScheduledMessageEventStore) Exists(ctx context.Context, event *scheduling.ScheduledMessageEvent) (bool, error) { func (s *PGScheduledMessageEventStore) Exists(ctx context.Context, event *scheduling.ScheduledMessageEvent) (bool, error) {
query := `SELECT EXISTS(SELECT 1 FROM scheduled_message_events WHERE scheduled_message_id = $1 AND recipient_id = $2)` query := `SELECT EXISTS(SELECT 1 FROM scheduled_message_events WHERE scheduled_message_id = $1 AND contact_id = $2)`
var exists bool var exists bool
err := s.db.QueryRow(ctx, query, event.ScheduledMessageId, event.RecipientId).Scan(&exists) err := s.db.QueryRow(ctx, query, event.ScheduledMessageId, event.ContactId).Scan(&exists)
if err != nil { if err != nil {
return false, fmt.Errorf("Scheduled message event does not exist: %w", err) return false, fmt.Errorf("Scheduled message event does not exist: %w", err)
} else { } else {
+1 -1
View File
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS scheduled_messages (
CREATE TABLE IF NOT EXISTS scheduled_message_events ( CREATE TABLE IF NOT EXISTS scheduled_message_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
recipient_id UUID NOT NULL, contact_id UUID NOT NULL,
message_id UUID NOT NULL, message_id UUID NOT NULL,
scheduled_message_id UUID NOT NULL, scheduled_message_id UUID NOT NULL,
created timestamptz DEFAULT now() created timestamptz DEFAULT now()