summaryrefslogtreecommitdiff
path: root/api/chat/chat.go
diff options
context:
space:
mode:
Diffstat (limited to 'api/chat/chat.go')
-rw-r--r--api/chat/chat.go195
1 files changed, 195 insertions, 0 deletions
diff --git a/api/chat/chat.go b/api/chat/chat.go
new file mode 100644
index 0000000..51ee47d
--- /dev/null
+++ b/api/chat/chat.go
@@ -0,0 +1,195 @@
+package chat
+
+import (
+ "encoding/json"
+ "github.com/golang-jwt/jwt/v5"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+ "time"
+
+ "git.simponic.xyz/simponic/phoneof/adapters/messaging"
+ "git.simponic.xyz/simponic/phoneof/api/types"
+ "git.simponic.xyz/simponic/phoneof/database"
+)
+
+func ValidateFren(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ fren_id := req.FormValue("fren_id")
+ fren, err := database.FindFren(context.DBConn, fren_id)
+ if err != nil || fren == nil {
+ log.Printf("err fetching friend %s %s", fren, err)
+ resp.WriteHeader(http.StatusUnauthorized)
+ return failure(context, req, resp)
+ }
+ context.User = fren
+ (*context.TemplateData)["User"] = fren
+ return success(context, req, resp)
+ }
+}
+
+func FetchMessagesContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ before := time.Now().UTC()
+ var err error
+ if req.FormValue("before") != "" {
+ before, err = time.Parse(time.RFC3339, req.FormValue("before"))
+ if err != nil {
+ log.Printf("bad time: %s", err)
+ resp.WriteHeader(http.StatusBadRequest)
+ return failure(context, req, resp)
+ }
+ }
+
+ query := database.ListMessageQuery{
+ FrenId: context.User.Id,
+ Before: before,
+ Limit: 25,
+ }
+ messages, err := database.ListMessages(context.DBConn, query)
+ if err != nil {
+ log.Printf("err listing messages %v %s", query, err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ (*context.TemplateData)["Messages"] = messages
+ return success(context, req, resp)
+ }
+}
+
+func SendMessageContinuation(messagingAdapter messaging.MessagingAdapter) func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ rawMessage := req.FormValue("message")
+ now := time.Now().UTC()
+ messageRequestId, err := messagingAdapter.SendMessage(context.User.Name + " " + rawMessage)
+ if err != nil {
+ log.Printf("err sending message %s %s %s", context.User, rawMessage, err)
+ // yeah this might be a 400 or whatever, i’ll fix it later
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ message, err := database.SaveMessage(context.DBConn, &database.Message{
+ Id: messageRequestId,
+ FrenId: context.User.Id,
+ Message: rawMessage,
+ Time: now,
+ FrenSent: true,
+ })
+
+ log.Printf("Saved message %v", message)
+ return success(context, req, resp)
+ }
+ }
+}
+
+type Timestamp struct {
+ time.Time
+}
+
+func (p *Timestamp) UnmarshalJSON(bytes []byte) error {
+ var raw string
+ err := json.Unmarshal(bytes, &raw)
+
+ if err != nil {
+ log.Printf("error decoding timestamp: %s\n", err)
+ return err
+ }
+
+ p.Time, err = time.Parse(time.RFC3339, raw)
+ if err != nil {
+ log.Printf("error decoding timestamp: %s\n", err)
+ return err
+ }
+
+ return nil
+}
+
+type HttpSmsEventData struct {
+ Contact string `json:"contact"`
+ Content string `json:"content"`
+ Owner string `json:"owner"`
+ Timestamp time.Time `json:"timestamp"`
+}
+type HttpSmsEvent struct {
+ Data HttpSmsEventData `json:"data"`
+ Type string `json:"type"`
+ Id string `json:"id"`
+}
+
+func ChatEventProcessorContinuation(signingKey string) func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ // check signing
+ joken := strings.Split(req.Header.Get("Authorization"), "Bearer ")
+ _, err := jwt.Parse(joken[1], func(token *jwt.Token) (interface{}, error) {
+ return []byte(signingKey), nil
+ })
+ if err != nil {
+ log.Printf("invalid jwt %s", err)
+ resp.WriteHeader(http.StatusBadRequest)
+ return failure(context, req, resp)
+ }
+
+ // decode the event
+ defer req.Body.Close()
+ body, err := io.ReadAll(req.Body)
+ if err != nil {
+ log.Printf("err reading body")
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ var event HttpSmsEvent
+ err = json.Unmarshal(body, &event)
+ if err != nil {
+ log.Printf("err unmarshaling body")
+ resp.WriteHeader(http.StatusBadRequest)
+ return failure(context, req, resp)
+ }
+
+ // we only care about received messages
+ if event.Type != "message.phone.received" {
+ log.Printf("got non-receive event %s", event.Type)
+ return success(context, req, resp)
+ }
+
+ // respond to texts with "<fren name> <content>"
+ content := strings.SplitN(event.Data.Content, " ", 2)
+ if len(content) < 2 {
+ log.Printf("no space delimiter")
+ resp.WriteHeader(http.StatusBadRequest)
+ return failure(context, req, resp)
+ }
+ name := content[0]
+ rawMessage := content[1]
+
+ fren, err := database.FindFrenByName(context.DBConn, name)
+ if err != nil {
+ log.Printf("err when getting fren %s %s", name, err)
+ resp.WriteHeader(http.StatusBadRequest)
+ return failure(context, req, resp)
+ }
+
+ // save the message!
+ _, err = database.SaveMessage(context.DBConn, &database.Message{
+ Id: event.Id,
+ FrenId: fren.Id,
+ Message: rawMessage,
+ Time: event.Data.Timestamp,
+ FrenSent: false,
+ })
+ if err != nil {
+ log.Printf("err when saving message %s %s", name, err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ return success(context, req, resp)
+ }
+ }
+
+}